summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--CMakeLists.txt31
-rw-r--r--COPYING340
-rw-r--r--README85
-rw-r--r--src/config.c561
-rw-r--r--src/dhcpv4.c508
-rw-r--r--src/dhcpv4.h93
-rw-r--r--src/dhcpv6-ia.c893
-rw-r--r--src/dhcpv6.c452
-rw-r--r--src/dhcpv6.h162
-rw-r--r--src/md5.c242
-rw-r--r--src/md5.h17
-rw-r--r--src/ndp.c532
-rw-r--r--src/ndp.h31
-rw-r--r--src/odhcpd.c423
-rw-r--r--src/odhcpd.h205
-rw-r--r--src/router.c502
-rw-r--r--src/router.h40
-rw-r--r--src/ubus.c314
19 files changed, 5445 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6bf011f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+.project
+.cproject
+odhcpd
+config.log
+CMakeCache.txt
+CMakeFiles
+CPackConfig.cmake
+CPackSourceConfig.cmake
+_CPack_Packages
+Makefile
+cmake_install.cmake
+install_manifest.txt
+*.deb
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..891dc41
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,31 @@
+cmake_minimum_required(VERSION 2.8)
+cmake_policy(SET CMP0015 NEW)
+
+# Project Definition
+project(odhcpd C)
+
+set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99")
+
+
+add_definitions(-D_GNU_SOURCE -Wall -Werror -Wextra -DWITH_UBUS)
+
+add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/md5.c src/dhcpv6-ia.c src/dhcpv4.c src/ubus.c)
+target_link_libraries(odhcpd resolv ubus ubox uci)
+
+# Installation
+install(TARGETS odhcpd DESTINATION sbin/)
+
+
+# Packaging information
+set(CPACK_PACKAGE_VERSION "1")
+set(CPACK_PACKAGE_CONTACT "Steven Barth <steven@midlink.org>")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "odhcpd")
+set(CPACK_GENERATOR "DEB;RPM;STGZ")
+set(CPACK_STRIP_FILES true)
+
+SET(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION})
+set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}")
+
+include(CPack)
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/README b/README
new file mode 100644
index 0000000..c1c0d1e
--- /dev/null
+++ b/README
@@ -0,0 +1,85 @@
+odhcpd - Embedded DHCP/DHCPv6/RA Server & Relay
+
+** Abstract **
+
+odhcpd is a daemon for serving and relaying IP management protocols to
+configure clients and downstream routers. It tries to follow the RFC 6204
+requirements for IPv6 home routers.
+
+odhcpd provides server services for DHCP, RA, stateless and stateful DHCPv6,
+prefix delegation and can be used to relay RA, DHCPv6 and NDP between routed
+(non-bridged) interfaces in case no delegated prefixes are available.
+
+
+** Features **
+
+1. Router Discovery support (solicitations and advertisements) with 2 modes
+ server: RD server for slave interfaces
+ a) automatic detection of prefixes, delegated prefix and default routes, MTU
+ b) automatic reannouncement when changes to prefixes or routes occur
+
+ relay: RD relay between master and slave interfaces
+ a) support for rewriting announced DNS-server addresses in relay mode
+
+3. DHCPv6-support with 2 modes of operation
+ server: minimalistic server mode
+ a) stateless and stateful address assignment
+ b) prefix delegation support
+ c) dynamic reconfiguration in case prefixes change
+ d) hostname detection and hosts-file creation
+
+ relay: mostly standards-compliant DHCPv6-relay
+ a) support for rewriting announced DNS-server addresses
+
+4. Proxy for Neighbor Discovery messages (solicitations and advertisments)
+ a) support for auto-learning routes to the local routing table
+ b) support for marking interfaces "external" not proxying NDP for them
+ and only serving NDP for DAD and for traffic to the router itself
+ [Warning: you should provide additional firewall rules for security]
+
+
+** Compiling **
+
+odhcpd uses cmake:
+* To prepare a Makefile use: "cmake ."
+* To build / install use: "make" / "make install" afterwards.
+* To build DEB or RPM packages use: "make package" afterwards.
+
+
+** Server Mode **
+
+0. Server mode is used as a minimalistic alternative for full-blown servers
+ like radvd or ISC DHCP if simplicity or a small footprint matter.
+ Note: The master interface is unused in this mode. It should be set to '.'.
+
+1. If there are non-local addresses assigned to the slave interface when a
+ router solicitation is received, said prefixes are announced automatically
+ for stateless autoconfiguration and also offered via stateful DHCPv6.
+ If all prefixes are bigger than /64 all but the first /64 of these prefixes
+ is offered via DHCPv6-PD to downstream routers.
+
+2. If DNS servers should be announced (DHCPv6 server-mode) then a local DNS-
+ proxy (e.g. dnsmasq) needs to be run on the router itself because 6relayd
+ will always announce a local address as DNS-server (if not otherwise
+ configured).
+
+3. odhcpd is run with the appropriate configuration.
+
+
+** Relay Mode **
+
+0. Relay mode is used when a /64-bit IPv6-Prefix should be distributed over
+ several links / isolated layer 2 domains (e.g. if no prefix delegation
+ is available). In this mode NDP (namely Router Discovery and Neighbor
+ Discovery) messages and DHCPv6-messages are proxied. For DHCPv6 also
+ server mode can be used instead of relaying if desired.
+
+1. When starting 6relayd it is required that the master interface - where
+ IPv6-service is already provided - is configured and usable.
+
+2. If the upstream router doesn't provide or announce a DNS-service that is
+ reachable in an at least site-local scope, a local DNS proxy (e.g. dnsmasq)
+ needs to be run and configued on the same router where 6relayd is running.
+ (This step can most likely be skipped in most environments.)
+
+3. odhcpd is run with the appropriate configuration.
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..bc85603
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,561 @@
+#include <resolv.h>
+#include <signal.h>
+#include <arpa/inet.h>
+
+#include <uci.h>
+#include <uci_blob.h>
+
+#include "odhcpd.h"
+
+static struct blob_buf b;
+struct list_head leases = LIST_HEAD_INIT(leases);
+struct list_head interfaces = LIST_HEAD_INIT(interfaces);
+struct config config = {false, NULL, NULL};
+
+enum {
+ IFACE_ATTR_INTERFACE,
+ IFACE_ATTR_IFNAME,
+ IFACE_ATTR_DYNAMICDHCP,
+ IFACE_ATTR_IGNORE,
+ IFACE_ATTR_LEASETIME,
+ IFACE_ATTR_LIMIT,
+ IFACE_ATTR_START,
+ IFACE_ATTR_MASTER,
+ IFACE_ATTR_UPSTREAM,
+ IFACE_ATTR_RA,
+ IFACE_ATTR_DHCPV4,
+ IFACE_ATTR_DHCPV6,
+ IFACE_ATTR_NDPROXY,
+ IFACE_ATTR_DNS,
+ IFACE_ATTR_DOMAIN,
+ IFACE_ATTR_ULA_COMPAT,
+ IFACE_ATTR_RA_DEFAULT,
+ IFACE_ATTR_RA_MANAGEMENT,
+ IFACE_ATTR_RA_OFFLINK,
+ IFACE_ATTR_RA_PREFERENCE,
+ IFACE_ATTR_NDPROXY_ROUTING,
+ IFACE_ATTR_NDPROXY_SLAVE,
+ IFACE_ATTR_NDPROXY_STATIC,
+ IFACE_ATTR_MAX
+};
+
+static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
+ [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_DYNAMICDHCP] = { .name = "dynamicdhcp", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_IGNORE] = { .name = "ignore", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_LEASETIME] = { .name = "leasetime", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_START] = { .name = "start", .type = BLOBMSG_TYPE_INT32 },
+ [IFACE_ATTR_LIMIT] = { .name = "limit", .type = BLOBMSG_TYPE_INT32 },
+ [IFACE_ATTR_MASTER] = { .name = "master", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_UPSTREAM] = { .name = "upstream", .type = BLOBMSG_TYPE_ARRAY },
+ [IFACE_ATTR_RA] = { .name = "ra", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_DHCPV4] = { .name = "dhcpv4", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_DHCPV6] = { .name = "dhcpv6", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_NDPROXY] = { .name = "ndproxy", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY },
+ [IFACE_ATTR_DOMAIN] = { .name = "domain", .type = BLOBMSG_TYPE_ARRAY },
+ [IFACE_ATTR_ULA_COMPAT] = { .name = "ula_compat", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_RA_DEFAULT] = { .name = "ra_default", .type = BLOBMSG_TYPE_INT32 },
+ [IFACE_ATTR_RA_MANAGEMENT] = { .name = "ra_management", .type = BLOBMSG_TYPE_INT32 },
+ [IFACE_ATTR_RA_OFFLINK] = { .name = "ra_offlink", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_RA_PREFERENCE] = { .name = "ra_preference", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_NDPROXY_ROUTING] = { .name = "ndproxy_routing", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_NDPROXY_SLAVE] = { .name = "ndproxy_slave", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_NDPROXY_STATIC] = { .name = "ndproxy_static", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+static const struct uci_blob_param_info iface_attr_info[IFACE_ATTR_MAX] = {
+ [IFACE_ATTR_UPSTREAM] = { .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_DNS] = { .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_DOMAIN] = { .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_NDPROXY_STATIC] = { .type = BLOBMSG_TYPE_STRING },
+};
+
+const struct uci_blob_param_list interface_attr_list = {
+ .n_params = IFACE_ATTR_MAX,
+ .params = iface_attrs,
+ .info = iface_attr_info,
+};
+
+
+enum {
+ LEASE_ATTR_IP,
+ LEASE_ATTR_MAC,
+ LEASE_ATTR_DUID,
+ LEASE_ATTR_HOSTID,
+ LEASE_ATTR_HOSTNAME,
+ LEASE_ATTR_MAX
+};
+
+
+static const struct blobmsg_policy lease_attrs[LEASE_ATTR_MAX] = {
+ [LEASE_ATTR_IP] = { .name = "ip", .type = BLOBMSG_TYPE_STRING },
+ [LEASE_ATTR_MAC] = { .name = "mac", .type = BLOBMSG_TYPE_STRING },
+ [LEASE_ATTR_DUID] = { .name = "duid", .type = BLOBMSG_TYPE_STRING },
+ [LEASE_ATTR_HOSTID] = { .name = "hostid", .type = BLOBMSG_TYPE_STRING },
+ [LEASE_ATTR_HOSTNAME] = { .name = "hostname", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+const struct uci_blob_param_list lease_attr_list = {
+ .n_params = LEASE_ATTR_MAX,
+ .params = lease_attrs,
+};
+
+
+enum {
+ ODHCPD_ATTR_LEGACY,
+ ODHCPD_ATTR_LEASEFILE,
+ ODHCPD_ATTR_LEASETRIGGER,
+ ODHCPD_ATTR_MAX
+};
+
+
+static const struct blobmsg_policy odhcpd_attrs[LEASE_ATTR_MAX] = {
+ [ODHCPD_ATTR_LEGACY] = { .name = "legacy", .type = BLOBMSG_TYPE_BOOL },
+ [ODHCPD_ATTR_LEASEFILE] = { .name = "leasefile", .type = BLOBMSG_TYPE_STRING },
+ [ODHCPD_ATTR_LEASETRIGGER] = { .name = "leasetrigger", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+const struct uci_blob_param_list odhcpd_attr_list = {
+ .n_params = ODHCPD_ATTR_MAX,
+ .params = odhcpd_attrs,
+};
+
+
+static struct interface* get_interface(const char *name)
+{
+ struct interface *c;
+ list_for_each_entry(c, &interfaces, head)
+ if (!strcmp(c->name, name))
+ return c;
+ return NULL;
+}
+
+
+static void clean_interface(struct interface *iface)
+{
+ free(iface->dns);
+ free(iface->search);
+ free(iface->upstream);
+ free(iface->static_ndp);
+ free(iface->dhcpv4_dns);
+ memset(&iface->ra, 0, sizeof(*iface) - offsetof(struct interface, ra));
+}
+
+
+static void close_interface(struct interface *iface)
+{
+ if (iface->head.next)
+ list_del(&iface->head);
+
+ setup_router_interface(iface, false);
+ setup_dhcpv6_interface(iface, false);
+ setup_ndp_interface(iface, false);
+ setup_dhcpv4_interface(iface, false);
+
+ clean_interface(iface);
+ free(iface);
+}
+
+
+static int parse_mode(const char *mode)
+{
+ if (!strcmp(mode, "disabled")) {
+ return RELAYD_DISABLED;
+ } else if (!strcmp(mode, "server")) {
+ return RELAYD_SERVER;
+ } else if (!strcmp(mode, "relay")) {
+ return RELAYD_RELAY;
+ } else if (!strcmp(mode, "hybrid")) {
+ return RELAYD_HYBRID;
+ } else {
+ return -1;
+ }
+}
+
+
+static void set_config(struct uci_section *s)
+{
+ struct blob_attr *tb[ODHCPD_ATTR_MAX], *c;
+
+ blob_buf_init(&b, 0);
+ uci_to_blob(&b, s, &lease_attr_list);
+ blobmsg_parse(lease_attrs, ODHCPD_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head));
+
+ if ((c = tb[ODHCPD_ATTR_LEGACY]))
+ config.legacy = blobmsg_get_bool(c);
+
+ if ((c = tb[ODHCPD_ATTR_LEASEFILE])) {
+ free(config.dhcp_statefile);
+ config.dhcp_statefile = strdup(blobmsg_get_string(c));
+ }
+
+ if ((c = tb[ODHCPD_ATTR_LEASETRIGGER])) {
+ free(config.dhcp_cb);
+ config.dhcp_cb = strdup(blobmsg_get_string(c));
+ }
+}
+
+
+static int set_lease(struct uci_section *s)
+{
+ struct blob_attr *tb[LEASE_ATTR_MAX], *c;
+
+ blob_buf_init(&b, 0);
+ uci_to_blob(&b, s, &lease_attr_list);
+ blobmsg_parse(lease_attrs, LEASE_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head));
+
+ size_t hostlen = 1;
+ if ((c = tb[LEASE_ATTR_HOSTNAME]))
+ hostlen = blobmsg_data_len(c);
+
+ struct lease *lease = calloc(1, sizeof(*lease) + hostlen);
+
+ if (hostlen > 1)
+ memcpy(lease->hostname, blobmsg_get_string(c), hostlen);
+
+ if ((c = tb[LEASE_ATTR_IP]))
+ if (inet_pton(AF_INET, blobmsg_get_string(c), &lease->ipaddr) < 0)
+ goto err;
+
+ if ((c = tb[LEASE_ATTR_MAC]))
+ if (!ether_aton_r(blobmsg_get_string(c), &lease->mac))
+ goto err;
+
+ if ((c = tb[LEASE_ATTR_DUID])) {
+ size_t duidlen = (blobmsg_data_len(c) - 1) / 2;
+ lease->duid = malloc(duidlen);
+ ssize_t len = odhcpd_unhexlify(lease->duid,
+ duidlen, blobmsg_get_string(c));
+
+ if (len < 0)
+ goto err;
+
+ lease->duid_len = len;
+ }
+
+ if ((c = tb[LEASE_ATTR_HOSTID]))
+ if (odhcpd_unhexlify((uint8_t*)&lease->hostid, sizeof(lease->hostid),
+ blobmsg_get_string(c)) < 0)
+ goto err;
+
+ list_add(&lease->head, &leases);
+ return 0;
+
+err:
+ free(lease->duid);
+ free(lease);
+ return -1;
+}
+
+
+int config_parse_interface(struct blob_attr *b, const char *name)
+{
+ bool overwrite = !!name;
+ struct blob_attr *tb[IFACE_ATTR_MAX], *c;
+ blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blob_data(b), blob_len(b));
+
+ if (tb[IFACE_ATTR_INTERFACE])
+ name = blobmsg_data(tb[IFACE_ATTR_INTERFACE]);
+
+ struct interface *iface = get_interface(name);
+ if (!iface) {
+ iface = calloc(1, sizeof(*iface));
+ strncpy(iface->name, name, sizeof(iface->name) - 1);
+ list_add(&iface->head, &interfaces);
+ } else {
+ clean_interface(iface);
+ }
+
+ const char *ifname = NULL;
+#ifdef WITH_UBUS
+ if (overwrite)
+ ifname = ubus_get_ifname(name);
+#endif
+ if ((c = tb[IFACE_ATTR_IFNAME]))
+ ifname = blobmsg_get_string(c);
+
+ strncpy(iface->ifname, ifname, sizeof(iface->ifname) - 1);
+ iface->inuse = true;
+
+ if (overwrite)
+ clean_interface(iface);
+
+ if ((c = tb[IFACE_ATTR_DYNAMICDHCP]))
+ iface->no_dynamic_dhcp = !blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_IGNORE]))
+ iface->ignore = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_LEASETIME])) {
+ char *val = blobmsg_get_string(c), *endptr;
+ double time = strtod(val, &endptr);
+ if (time && endptr[0]) {
+ if (endptr[0] == 's')
+ time *= 1;
+ else if (endptr[0] == 'm')
+ time *= 60;
+ else if (endptr[0] == 'h')
+ time *= 3600;
+ else if (endptr[0] == 'd')
+ time *= 24 * 3600;
+ else if (endptr[0] == 'w')
+ time *= 7 * 24 * 3600;
+ else
+ goto err;
+ }
+
+ if (time >= 60)
+ iface->dhcpv4_leasetime = time;
+ }
+
+ if ((c = tb[IFACE_ATTR_START])) {
+ iface->dhcpv4_start.s_addr = htonl(blobmsg_get_u32(c));
+
+ if (config.legacy)
+ iface->dhcpv4 = RELAYD_SERVER;
+ }
+
+ if ((c = tb[IFACE_ATTR_LIMIT]))
+ iface->dhcpv4_end.s_addr = htonl(
+ ntohl(iface->dhcpv4_start.s_addr) + blobmsg_get_u32(c));
+
+ if ((c = tb[IFACE_ATTR_MASTER]))
+ iface->master = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_UPSTREAM])) {
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, c, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, NULL))
+ continue;
+
+ iface->upstream = realloc(iface->upstream,
+ iface->upstream_len + blobmsg_data_len(cur));
+ memcpy(iface->upstream + iface->upstream_len, blobmsg_get_string(cur), blobmsg_data_len(cur));
+ iface->upstream_len += blobmsg_data_len(cur);
+ }
+ }
+
+ if ((c = tb[IFACE_ATTR_RA]))
+ if ((iface->ra = parse_mode(blobmsg_get_string(c))) < 0)
+ goto err;
+
+ if ((c = tb[IFACE_ATTR_DHCPV4]))
+ if ((iface->dhcpv4 = parse_mode(blobmsg_get_string(c))) < 0)
+ goto err;
+
+ if ((c = tb[IFACE_ATTR_DHCPV6]))
+ if ((iface->dhcpv6 = parse_mode(blobmsg_get_string(c))) < 0)
+ goto err;
+
+ if ((c = tb[IFACE_ATTR_NDPROXY]))
+ iface->ndp = blobmsg_get_bool(c) ? RELAYD_RELAY : RELAYD_DISABLED;
+
+ if ((c = tb[IFACE_ATTR_DNS])) {
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, c, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, NULL))
+ continue;
+
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ if (inet_pton(AF_INET, blobmsg_get_string(cur), &addr4) == 1) {
+ iface->dhcpv4_dns = realloc(iface->dhcpv4_dns,
+ (++iface->dhcpv4_dns_cnt) * sizeof(*iface->dhcpv4_dns));
+ iface->dhcpv4_dns[iface->dhcpv4_dns_cnt - 1] = addr4;
+ } else if (inet_pton(AF_INET6, blobmsg_get_string(cur), &addr6) == 1) {
+ iface->dns = realloc(iface->dns,
+ (++iface->dns_cnt) * sizeof(*iface->dns));
+ iface->dns[iface->dns_cnt - 1] = addr6;
+ } else {
+ goto err;
+ }
+ }
+ }
+
+ if ((c = tb[IFACE_ATTR_DOMAIN])) {
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, c, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, NULL))
+ continue;
+
+ uint8_t buf[256];
+ int len = dn_comp(blobmsg_get_string(cur), buf, sizeof(buf), NULL, NULL);
+ if (len <= 0)
+ goto err;
+
+ iface->search = realloc(iface->search, iface->search_len + len);
+ memcpy(&iface->search[iface->search_len], buf, len);
+ iface->search_len += len;
+ }
+ }
+
+ if ((c = tb[IFACE_ATTR_ULA_COMPAT]))
+ iface->deprecate_ula_if_public_avail = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_RA_DEFAULT]))
+ iface->default_router = blobmsg_get_u32(c);
+
+ if ((c = tb[IFACE_ATTR_RA_MANAGEMENT]))
+ iface->managed = blobmsg_get_u32(c);
+
+ if ((c = tb[IFACE_ATTR_RA_OFFLINK]))
+ iface->ra_not_onlink = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_RA_PREFERENCE])) {
+ const char *prio = blobmsg_get_string(c);
+
+ if (!strcmp(prio, "high"))
+ iface->route_preference = 1;
+ else if (!strcmp(prio, "low"))
+ iface->route_preference = -1;
+ else if (!strcmp(prio, "medium") || !strcmp(prio, "default"))
+ iface->route_preference = 0;
+ else
+ goto err;
+ }
+
+ if ((c = tb[IFACE_ATTR_NDPROXY_ROUTING]))
+ iface->learn_routes = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_NDPROXY_SLAVE]))
+ iface->external = blobmsg_get_bool(c);
+
+ if ((c = tb[IFACE_ATTR_NDPROXY_STATIC])) {
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, c, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, NULL))
+ continue;
+
+ int len = blobmsg_data_len(cur);
+ iface->static_ndp = realloc(iface->static_ndp, iface->static_ndp_len + len);
+ memcpy(&iface->static_ndp[iface->static_ndp_len], blobmsg_get_string(cur), len);
+ iface->static_ndp_len += len;
+ }
+ }
+
+ iface->ignore = (iface->ifindex = if_nametoindex(iface->ifname)) < 0;
+ return 0;
+
+err:
+ close_interface(iface);
+ return -1;
+}
+
+static int set_interface(struct uci_section *s)
+{
+ blob_buf_init(&b, 0);
+ uci_to_blob(&b, s, &interface_attr_list);
+ return config_parse_interface(b.head, s->e.name);
+}
+
+
+static volatile bool do_reload = false;
+static void set_stop(int signal)
+{
+ uloop_end();
+ do_reload = (signal == SIGHUP);
+}
+
+void odhcpd_run(void)
+{
+ struct uci_context *uci = uci_alloc_context();
+ signal(SIGTERM, set_stop);
+ signal(SIGHUP, set_stop);
+ signal(SIGINT, set_stop);
+
+ do {
+ do_reload = false;
+
+ struct lease *l;
+ list_for_each_entry(l, &leases, head) {
+ list_del(&l->head);
+ free(l->duid);
+ free(l);
+ }
+
+ struct uci_package *dhcp = NULL;
+ if (!uci_load(uci, "dhcp", &dhcp)) {
+ struct uci_element *e;
+ uci_foreach_element(&dhcp->sections, e) {
+ struct uci_section *s = uci_to_section(e);
+ if (!strcmp(s->type, "lease"))
+ set_lease(s);
+ else if (!strcmp(s->type, "odhcpd"))
+ set_config(s);
+ }
+
+ uci_foreach_element(&dhcp->sections, e) {
+ struct uci_section *s = uci_to_section(e);
+ if (!strcmp(s->type, "dhcp"))
+ set_interface(s);
+ }
+ }
+
+#ifdef WITH_UBUS
+ ubus_apply_network();
+#endif
+
+ // Evaluate hybrid mode for master
+ struct interface *master = NULL, *i;
+ list_for_each_entry(i, &interfaces, head) {
+ if (!i->master)
+ continue;
+
+ enum odhcpd_mode hybrid_mode = RELAYD_DISABLED;
+ if (i->dhcpv6 == RELAYD_HYBRID)
+ i->dhcpv6 = hybrid_mode;
+
+ if (i->ra == RELAYD_HYBRID)
+ i->ra = hybrid_mode;
+
+ if (i->ndp == RELAYD_HYBRID)
+ i->ndp = hybrid_mode;
+
+ if (i->dhcpv6 == RELAYD_RELAY || i->ra == RELAYD_RELAY || i->ndp == RELAYD_RELAY)
+ master = i;
+ }
+
+
+ list_for_each_entry(i, &interfaces, head) {
+ if (i->inuse && !i->ignore) {
+ // Resolve hybrid mode
+ if (i->dhcpv6 == RELAYD_HYBRID)
+ i->dhcpv6 = (master && master->dhcpv6 == RELAYD_RELAY) ?
+ RELAYD_RELAY : RELAYD_SERVER;
+
+ if (i->ra == RELAYD_HYBRID)
+ i->ra = (master && master->ra == RELAYD_RELAY) ?
+ RELAYD_RELAY : RELAYD_SERVER;
+
+ if (i->ndp == RELAYD_HYBRID)
+ i->ndp = (master && master->ndp == RELAYD_RELAY) ?
+ RELAYD_RELAY : RELAYD_SERVER;
+
+ setup_router_interface(i, true);
+ setup_dhcpv6_interface(i, true);
+ setup_ndp_interface(i, true);
+ setup_dhcpv4_interface(i, true);
+ } else {
+ close_interface(i);
+ }
+ }
+
+ uloop_run();
+
+ if (dhcp)
+ uci_unload(uci, dhcp);
+ } while (do_reload);
+}
+
diff --git a/src/dhcpv4.c b/src/dhcpv4.c
new file mode 100644
index 0000000..38b4f9b
--- /dev/null
+++ b/src/dhcpv4.c
@@ -0,0 +1,508 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <resolv.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ip.h>
+#include <sys/ioctl.h>
+#include <sys/timerfd.h>
+#include <arpa/inet.h>
+
+#include "odhcpd.h"
+#include "dhcpv4.h"
+#include "dhcpv6.h"
+
+
+static void handle_dhcpv4(void *addr, void *data, size_t len,
+ struct interface *iface);
+static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
+ enum dhcpv4_msg msg, const uint8_t *mac, struct in_addr reqaddr,
+ const char *hostname);
+
+
+// Create socket and register events
+int init_dhcpv4(void)
+{
+ return 0;
+}
+
+
+int setup_dhcpv4_interface(struct interface *iface, bool enable)
+{
+ if (iface->dhcpv4_event.uloop.fd > 0) {
+ close(iface->dhcpv4_event.uloop.fd);
+ iface->dhcpv4_event.uloop.fd = -1;
+ }
+
+ if (iface->dhcpv4 && enable) {
+ if (!iface->dhcpv4_assignments.next)
+ INIT_LIST_HEAD(&iface->dhcpv4_assignments);
+
+ int sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+
+ // Basic IPv6 configuration
+ int val = 1;
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+ setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val));
+ setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val));
+
+ val = IPTOS_CLASS_CS6;
+ setsockopt(sock, IPPROTO_IP, IP_TOS, &val, sizeof(val));
+
+ val = IP_PMTUDISC_DONT;
+ setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
+
+ setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE,
+ iface->ifname, strlen(iface->ifname));
+
+ struct sockaddr_in bind_addr = {AF_INET, htons(DHCPV4_SERVER_PORT),
+ {INADDR_ANY}, {0}};
+
+ if (bind(sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr))) {
+ syslog(LOG_ERR, "Failed to open DHCPv4 server socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+
+ if (ntohl(iface->dhcpv4_start.s_addr) > ntohl(iface->dhcpv4_end.s_addr)) {
+ syslog(LOG_ERR, "Invalid DHCP range");
+ return -1;
+ }
+
+ // Create a range if not specified
+ struct ifreq ifreq;
+ strncpy(ifreq.ifr_name, iface->ifname, sizeof(ifreq.ifr_name));
+
+ struct sockaddr_in *saddr = (struct sockaddr_in*)&ifreq.ifr_addr;
+ struct sockaddr_in *smask = (struct sockaddr_in*)&ifreq.ifr_netmask;
+ if (!(iface->dhcpv4_start.s_addr & htonl(0xffff0000)) &&
+ !(iface->dhcpv4_end.s_addr & htonl(0xffff0000)) &&
+ !ioctl(sock, SIOCGIFADDR, &ifreq)) {
+ struct in_addr addr = saddr->sin_addr;
+
+ ioctl(sock, SIOCGIFNETMASK, &ifreq);
+ struct in_addr mask = smask->sin_addr;
+
+ uint32_t start = ntohl(iface->dhcpv4_start.s_addr);
+ uint32_t end = ntohl(iface->dhcpv4_end.s_addr);
+
+ if (start && end && start < end &&
+ start > ntohl(addr.s_addr & ~mask.s_addr) &&
+ (start & ntohl(mask.s_addr)) == start &&
+ (end & ntohl(mask.s_addr)) == end) {
+ iface->dhcpv4_start.s_addr = htonl(start) |
+ (addr.s_addr & mask.s_addr);
+ iface->dhcpv4_end.s_addr = htonl(end) |
+ (addr.s_addr & mask.s_addr);
+ } else if (ntohl(mask.s_addr) <= 0xffffffc0) {
+ start = addr.s_addr & mask.s_addr;
+ end = addr.s_addr & mask.s_addr;
+
+ if (ntohl(mask.s_addr) <= 0xffffff00) {
+ iface->dhcpv4_start.s_addr = start | htonl(20);
+ iface->dhcpv4_end.s_addr = end | htonl(199);
+ } else {
+ iface->dhcpv4_start.s_addr = start | htonl(10);
+ iface->dhcpv4_end.s_addr = end | htonl(59);
+ }
+ }
+
+
+ }
+
+ // Parse static entries
+ struct lease *lease;
+ list_for_each_entry(lease, &leases, head) {
+ // Construct entry
+ size_t hostlen = strlen(lease->hostname) + 1;
+ struct dhcpv4_assignment *a = calloc(1, sizeof(*a) + hostlen);
+
+ a->addr = ntohl(lease->ipaddr.s_addr);
+ memcpy(a->hwaddr, lease->mac.ether_addr_octet, sizeof(a->hwaddr));
+ memcpy(a->hostname, lease->hostname, hostlen);
+
+ // Assign to all interfaces
+ struct dhcpv4_assignment *c;
+ list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+ if (c->addr > a->addr) {
+ list_add_tail(&a->head, &c->head);
+ } else if (c->addr == a->addr) {
+ // Already an assignment with that number
+ break;
+ }
+ }
+
+ if (!a->head.next)
+ free(a);
+ }
+
+ // Clean invalid assignments
+ struct dhcpv4_assignment *a, *n;
+ list_for_each_entry_safe(a, n, &iface->dhcpv4_assignments, head) {
+ if ((htonl(a->addr) & smask->sin_addr.s_addr) !=
+ (saddr->sin_addr.s_addr & smask->sin_addr.s_addr)) {
+ list_del(&a->head);
+ free(a);
+ }
+ }
+
+
+ if (iface->dhcpv4_leasetime < 60)
+ iface->dhcpv4_leasetime = 1800;
+
+ iface->dhcpv4_event.uloop.fd = sock;
+ iface->dhcpv4_event.handle_dgram = handle_dhcpv4;
+ odhcpd_register(&iface->dhcpv4_event);
+ } else if (iface->dhcpv4_assignments.next) {
+ while (!list_empty(&iface->dhcpv4_assignments)) {
+ struct dhcpv4_assignment *a = list_first_entry(&iface->dhcpv4_assignments,
+ struct dhcpv4_assignment, head);
+ list_del(&a->head);
+ free(a->hostname);
+ free(a);
+ }
+
+ }
+ return 0;
+}
+
+
+static void dhcpv4_put(struct dhcpv4_message *msg, uint8_t **cookie,
+ uint8_t type, uint8_t len, const void *data)
+{
+ uint8_t *c = *cookie;
+ if (*cookie + 2 + len > (uint8_t*)&msg[1])
+ return;
+
+ *c++ = type;
+ *c++ = len;
+ memcpy(c, data, len);
+
+ *cookie = c + len;
+}
+
+
+// Simple DHCPv6-server for information requests
+static void handle_dhcpv4(void *addr, void *data, size_t len,
+ struct interface *iface)
+{
+ if (!iface->dhcpv4)
+ return;
+
+ struct dhcpv4_message *req = data;
+ if (len < offsetof(struct dhcpv4_message, options) + 4 ||
+ req->op != DHCPV4_BOOTREQUEST || req->hlen != 6)
+ return;
+
+ int sock = iface->dhcpv4_event.uloop.fd;
+ struct sockaddr_in ifaddr;
+ struct sockaddr_in ifnetmask;
+
+ syslog(LOG_NOTICE, "Got DHCPv4 request");
+
+ struct ifreq ifreq;
+ memcpy(ifreq.ifr_name, iface->ifname, sizeof(ifreq.ifr_name));
+ if (ioctl(sock, SIOCGIFADDR, &ifreq)) {
+ syslog(LOG_WARNING, "DHCPv4 failed to detect address: %s", strerror(errno));
+ return;
+ }
+
+ memcpy(&ifaddr, &ifreq.ifr_addr, sizeof(ifaddr));
+ if (ioctl(sock, SIOCGIFNETMASK, &ifreq))
+ return;
+
+ memcpy(&ifnetmask, &ifreq.ifr_netmask, sizeof(ifnetmask));
+ uint32_t network = ifaddr.sin_addr.s_addr & ifnetmask.sin_addr.s_addr;
+
+ if ((iface->dhcpv4_start.s_addr & ifnetmask.sin_addr.s_addr) != network ||
+ (iface->dhcpv4_end.s_addr & ifnetmask.sin_addr.s_addr) != network) {
+ syslog(LOG_WARNING, "DHCPv4 range out of assigned network");
+ return;
+ }
+
+ struct ifreq ifr = {.ifr_name = ""};
+ strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
+
+ struct dhcpv4_message reply = {
+ .op = DHCPV4_BOOTREPLY,
+ .htype = 1,
+ .hlen = 6,
+ .hops = 0,
+ .xid = req->xid,
+ .secs = 0,
+ .flags = req->flags,
+ .ciaddr = {INADDR_ANY},
+ .giaddr = req->giaddr,
+ .siaddr = ifaddr.sin_addr,
+ };
+ memcpy(reply.chaddr, req->chaddr, sizeof(reply.chaddr));
+
+ reply.options[0] = 0x63;
+ reply.options[1] = 0x82;
+ reply.options[2] = 0x53;
+ reply.options[3] = 0x63;
+
+ uint8_t *cookie = &reply.options[4];
+ uint8_t reqmsg = DHCPV4_MSG_REQUEST;
+ uint8_t msg = DHCPV4_MSG_ACK;
+
+ struct in_addr reqaddr = {INADDR_ANY};
+ char hostname[256];
+ hostname[0] = 0;
+
+ uint8_t *start = &req->options[4];
+ uint8_t *end = ((uint8_t*)data) + len;
+ struct dhcpv4_option *opt;
+ dhcpv4_for_each_option(start, end, opt) {
+ if (opt->type == DHCPV4_OPT_MESSAGE && opt->len == 1) {
+ reqmsg = opt->data[0];
+ } else if (opt->type == DHCPV4_OPT_HOSTNAME && opt->len > 0) {
+ memcpy(hostname, opt->data, opt->len);
+ hostname[opt->len] = 0;
+ } else if (opt->type == DHCPV4_OPT_IPADDRESS && opt->len == 4) {
+ memcpy(&reqaddr, opt->data, 4);
+ } else if (opt->type == DHCPV4_OPT_SERVERID && opt->len == 4) {
+ if (memcmp(opt->data, &ifaddr.sin_addr, 4))
+ return;
+ }
+ }
+
+ if (reqmsg != DHCPV4_MSG_DISCOVER && reqmsg != DHCPV4_MSG_REQUEST &&
+ reqmsg != DHCPV4_MSG_INFORM && reqmsg != DHCPV4_MSG_DECLINE &&
+ reqmsg != DHCPV4_MSG_RELEASE)
+ return;
+
+ struct dhcpv4_assignment *lease = NULL;
+ if (reqmsg != DHCPV4_MSG_INFORM)
+ lease = dhcpv4_lease(iface, reqmsg, req->chaddr, reqaddr, hostname);
+
+ if (!lease) {
+ if (reqmsg == DHCPV4_MSG_REQUEST)
+ msg = DHCPV4_MSG_NAK;
+ else if (reqmsg == DHCPV4_MSG_DISCOVER)
+ return;
+ } else if (reqmsg == DHCPV4_MSG_DISCOVER) {
+ msg = DHCPV4_MSG_OFFER;
+ }
+
+ if (reqmsg == DHCPV4_MSG_DECLINE || reqmsg == DHCPV4_MSG_RELEASE)
+ return;
+
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_MESSAGE, 1, &msg);
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_SERVERID, 4, &ifaddr.sin_addr);
+
+ if (lease) {
+ reply.yiaddr.s_addr = htonl(lease->addr);
+
+ uint32_t val = htonl(iface->dhcpv4_leasetime);
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_LEASETIME, 4, &val);
+
+ val = htonl(500 * iface->dhcpv4_leasetime / 1000);
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_RENEW, 4, &val);
+
+ val = htonl(875 * iface->dhcpv4_leasetime / 1000);
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_REBIND, 4, &val);
+
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NETMASK, 4, &ifnetmask.sin_addr);
+
+ if (lease->hostname[0])
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_HOSTNAME,
+ strlen(lease->hostname), lease->hostname);
+
+ if (!ioctl(sock, SIOCGIFBRDADDR, &ifr)) {
+ struct sockaddr_in *ina = (struct sockaddr_in*)&ifr.ifr_broadaddr;
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_BROADCAST, 4, &ina->sin_addr);
+ }
+ }
+
+ if (!ioctl(sock, SIOCGIFMTU, &ifr)) {
+ uint16_t mtu = htons(ifr.ifr_mtu);
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_MTU, 2, &mtu);
+ }
+
+ if (iface->search) {
+ char b[256];
+ if (dn_expand(iface->search, iface->search + iface->search_len,
+ iface->search, b, sizeof(b)) > 0)
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DOMAIN, strlen(b), b);
+ } else if (!res_init() && _res.dnsrch[0] && _res.dnsrch[0][0]) {
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DOMAIN,
+ strlen(_res.dnsrch[0]), _res.dnsrch[0]);
+ }
+
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_ROUTER, 4, &ifaddr.sin_addr);
+
+
+
+ if (iface->dhcpv4_dns_cnt == 0)
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER, 4, &ifaddr.sin_addr);
+ else
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER,
+ 4 * iface->dhcpv4_dns_cnt, iface->dhcpv4_dns);
+
+
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_END, 0, NULL);
+
+ struct sockaddr_in dest = *((struct sockaddr_in*)addr);
+ if (req->giaddr.s_addr) {
+ dest.sin_addr = req->giaddr;
+ dest.sin_port = htons(DHCPV4_SERVER_PORT);
+ } else if (req->ciaddr.s_addr && req->ciaddr.s_addr != dest.sin_addr.s_addr) {
+ dest.sin_addr = req->ciaddr;
+ dest.sin_port = htons(DHCPV4_CLIENT_PORT);
+ } else if ((ntohs(req->flags) & DHCPV4_FLAG_BROADCAST) ||
+ req->hlen != reply.hlen) {
+ dest.sin_addr.s_addr = INADDR_BROADCAST;
+ dest.sin_port = htons(DHCPV4_CLIENT_PORT);
+ } else {
+ dest.sin_addr = reply.yiaddr;
+ dest.sin_port = htons(DHCPV4_CLIENT_PORT);
+
+ struct arpreq arp = {.arp_flags = ATF_COM};
+ memcpy(arp.arp_ha.sa_data, req->chaddr, 6);
+ memcpy(&arp.arp_pa, &dest, sizeof(arp.arp_pa));
+ memcpy(arp.arp_dev, iface->ifname, sizeof(arp.arp_dev));
+ ioctl(sock, SIOCSARP, &arp);
+ }
+
+ sendto(sock, &reply, sizeof(reply), MSG_DONTWAIT,
+ (struct sockaddr*)&dest, sizeof(dest));
+}
+
+
+static bool dhcpv4_assign(struct interface *iface,
+ struct dhcpv4_assignment *assign, uint32_t raddr)
+{
+ const unsigned tries = 10;
+ uint32_t start = ntohl(iface->dhcpv4_start.s_addr);
+ uint32_t end = ntohl(iface->dhcpv4_end.s_addr);
+ uint32_t count = end - start + 1;
+
+ // Seed RNG with checksum of DUID
+ uint32_t seed = 0;
+ for (size_t i = 0; i < sizeof(assign->hwaddr); ++i)
+ seed += assign->hwaddr[i];
+ srand(seed);
+
+ // Try to assign up to 100x
+ for (unsigned i = 0; i < tries; ++i) {
+ uint32_t try = (((uint32_t)rand()) % count) + start;
+ if (i == 0 && raddr >= start && raddr <= end)
+ try = raddr;
+ else if (i == tries - 1)
+ try = start;
+
+ if (list_empty(&iface->dhcpv4_assignments)) {
+ assign->addr = try;
+ list_add(&assign->head, &iface->dhcpv4_assignments);
+ return true;
+ }
+
+ struct dhcpv4_assignment *c;
+ list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+ if (c->addr > try) {
+ assign->addr = try;
+ list_add_tail(&assign->head, &c->head);
+ return true;
+ } else if (c->addr == try) {
+ if (i < tries - 1)
+ break;
+ else
+ ++try;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
+ enum dhcpv4_msg msg, const uint8_t *mac, struct in_addr reqaddr,
+ const char *hostname)
+{
+ struct dhcpv4_assignment *lease = NULL;
+ uint32_t raddr = ntohl(reqaddr.s_addr);
+ time_t now = odhcpd_time();
+
+ struct dhcpv4_assignment *c, *n, *a = NULL;
+ list_for_each_entry_safe(c, n, &iface->dhcpv4_assignments, head) {
+ if (c->addr == raddr && !memcmp(c->hwaddr, mac, 6)) {
+ a = c;
+ break;
+ } else if (c->valid_until < now) {
+ list_del(&c->head);
+ free(c);
+ }
+ }
+
+ bool update_state = false;
+ if (msg == DHCPV4_MSG_DISCOVER || msg == DHCPV4_MSG_REQUEST) {
+ bool assigned = !!a;
+ size_t hostlen = strlen(hostname) + 1;
+
+ if (!a && !iface->no_dynamic_dhcp) { // Create new binding
+ a = calloc(1, sizeof(*a) + hostlen);
+ memcpy(a->hwaddr, mac, sizeof(a->hwaddr));
+ memcpy(a->hostname, hostname, hostlen);
+
+ assigned = dhcpv4_assign(iface, a, raddr);
+ }
+
+ if (assigned && !a->hostname[0] && hostname) {
+ a = realloc(a, sizeof(*a) + hostlen);
+ memcpy(a->hostname, hostname, hostlen);
+
+ // Fixup list
+ a->head.next->prev = &a->head;
+ a->head.prev->next = &a->head;
+ }
+
+ // Was only a solicitation: mark binding for removal
+ if (assigned && a->valid_until < now) {
+ a->valid_until = (msg == DHCPV4_MSG_DISCOVER) ? 0 :
+ (now + iface->dhcpv4_leasetime);
+ } else if (!assigned && a) { // Cleanup failed assignment
+ free(a);
+ a = NULL;
+ }
+
+ if (assigned && a)
+ lease = a;
+ } else if (msg == DHCPV4_MSG_RELEASE) {
+ if (a) {
+ a->valid_until = 0;
+ update_state = true;
+ }
+ } else if (msg == DHCPV4_MSG_DECLINE) {
+ memset(a->hwaddr, 0, sizeof(a->hwaddr));
+ a->valid_until = now + 3600; // Block address for 1h
+ update_state = true;
+ }
+
+ if (update_state)
+ dhcpv6_write_statefile();
+
+ return lease;
+}
+
diff --git a/src/dhcpv4.h b/src/dhcpv4.h
new file mode 100644
index 0000000..308cc53
--- /dev/null
+++ b/src/dhcpv4.h
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ */
+#pragma once
+
+#define DHCPV4_CLIENT_PORT 68
+#define DHCPV4_SERVER_PORT 67
+
+#define DHCPV4_FLAG_BROADCAST 0x8000
+
+enum dhcpv4_op {
+ DHCPV4_BOOTREQUEST = 1,
+ DHCPV4_BOOTREPLY = 2
+};
+
+enum dhcpv4_msg {
+ DHCPV4_MSG_DISCOVER = 1,
+ DHCPV4_MSG_OFFER = 2,
+ DHCPV4_MSG_REQUEST = 3,
+ DHCPV4_MSG_DECLINE = 4,
+ DHCPV4_MSG_ACK = 5,
+ DHCPV4_MSG_NAK = 6,
+ DHCPV4_MSG_RELEASE = 7,
+ DHCPV4_MSG_INFORM = 8,
+};
+
+enum dhcpv4_opt {
+ DHCPV4_OPT_NETMASK = 1,
+ DHCPV4_OPT_ROUTER = 3,
+ DHCPV4_OPT_DNSSERVER = 6,
+ DHCPV4_OPT_DOMAIN = 15,
+ DHCPV4_OPT_MTU = 26,
+ DHCPV4_OPT_BROADCAST = 28,
+ DHCPV4_OPT_NTPSERVER = 42,
+ DHCPV4_OPT_LEASETIME = 51,
+ DHCPV4_OPT_MESSAGE = 53,
+ DHCPV4_OPT_SERVERID = 54,
+ DHCPV4_OPT_RENEW = 58,
+ DHCPV4_OPT_REBIND = 59,
+ DHCPV4_OPT_IPADDRESS = 50,
+ DHCPV4_OPT_HOSTNAME = 10,
+ DHCPV4_OPT_REQUEST = 17,
+ DHCPV4_OPT_END = 255,
+};
+
+struct dhcpv4_message {
+ uint8_t op;
+ uint8_t htype;
+ uint8_t hlen;
+ uint8_t hops;
+ uint32_t xid;
+ uint16_t secs;
+ uint16_t flags;
+ struct in_addr ciaddr;
+ struct in_addr yiaddr;
+ struct in_addr siaddr;
+ struct in_addr giaddr;
+ uint8_t chaddr[16];
+ char sname[64];
+ char file[128];
+ uint8_t options[312];
+};
+
+struct dhcpv4_assignment {
+ struct list_head head;
+ uint32_t addr;
+ time_t valid_until;
+ uint8_t hwaddr[6];
+ char hostname[];
+};
+
+struct dhcpv4_option {
+ uint8_t type;
+ uint8_t len;
+ uint8_t data[];
+};
+
+
+#define dhcpv4_for_each_option(start, end, opt)\
+ for (opt = (struct dhcpv4_option*)(start); \
+ &opt[1] <= (struct dhcpv4_option*)(end) && \
+ &opt->data[opt->len] <= (end); \
+ opt = (struct dhcpv4_option*)&opt->data[opt->len])
diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c
new file mode 100644
index 0000000..89d3a15
--- /dev/null
+++ b/src/dhcpv6-ia.c
@@ -0,0 +1,893 @@
+/**
+ * Copyright (C) 2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "odhcpd.h"
+#include "dhcpv6.h"
+#include "dhcpv4.h"
+#include "md5.h"
+
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <alloca.h>
+#include <resolv.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/timerfd.h>
+
+
+static void update(struct interface *iface);
+static void reconf_timer(struct uloop_timeout *event);
+static struct uloop_timeout reconf_event = {.cb = reconf_timer};
+static int socket_fd = -1;
+static uint32_t serial = 0;
+
+
+int dhcpv6_ia_init(int dhcpv6_socket)
+{
+ socket_fd = dhcpv6_socket;
+ uloop_timeout_set(&reconf_event, 2000);
+ return 0;
+}
+
+
+int setup_dhcpv6_ia_interface(struct interface *iface, bool enable)
+{
+ if (!enable && iface->ia_assignments.next) {
+ struct dhcpv6_assignment *c;
+ while (!list_empty(&iface->ia_assignments)) {
+ c = list_first_entry(&iface->ia_assignments, struct dhcpv6_assignment, head);
+ list_del(&c->head);
+ free(c);
+ }
+ }
+
+ if (iface->dhcpv6 == RELAYD_SERVER) {
+ if (!iface->ia_assignments.next)
+ INIT_LIST_HEAD(&iface->ia_assignments);
+
+ if (list_empty(&iface->ia_assignments)) {
+ struct dhcpv6_assignment *border = calloc(1, sizeof(*border));
+ border->length = 64;
+ list_add(&border->head, &iface->ia_assignments);
+ }
+
+ update(iface);
+
+ // Parse static entries
+ struct lease *lease;
+ list_for_each_entry(lease, &leases, head) {
+ // Construct entry
+ struct dhcpv6_assignment *a = calloc(1, sizeof(*a) + lease->duid_len);
+ a->clid_len = lease->duid_len;
+ a->length = 128;
+ a->assigned = lease->hostid;
+ odhcpd_urandom(a->key, sizeof(a->key));
+ memcpy(a->clid_data, lease->duid, a->clid_len);
+ memcpy(a->mac, lease->mac.ether_addr_octet, sizeof(a->mac));
+
+ // Assign to all interfaces
+ struct dhcpv6_assignment *c;
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (c->length != 128 || c->assigned > a->assigned) {
+ list_add_tail(&a->head, &c->head);
+ } else if (c->assigned == a->assigned) {
+ // Already an assignment with that number
+ break;
+ }
+ }
+
+ if (a->head.next) {
+ if (lease->hostname[0]) {
+ free(a->hostname);
+ a->hostname = strdup(lease->hostname);
+ }
+ } else {
+ free(a);
+ }
+ }
+ }
+ return 0;
+}
+
+
+static int send_reconf(struct interface *iface, struct dhcpv6_assignment *assign)
+{
+ struct {
+ struct dhcpv6_client_header hdr;
+ uint16_t srvid_type;
+ uint16_t srvid_len;
+ uint16_t duid_type;
+ uint16_t hardware_type;
+ uint8_t mac[6];
+ uint16_t msg_type;
+ uint16_t msg_len;
+ uint8_t msg_id;
+ struct dhcpv6_auth_reconfigure auth;
+ uint16_t clid_type;
+ uint16_t clid_len;
+ uint8_t clid_data[128];
+ } __attribute__((packed)) reconf_msg = {
+ .hdr = {DHCPV6_MSG_RECONFIGURE, {0, 0, 0}},
+ .srvid_type = htons(DHCPV6_OPT_SERVERID),
+ .srvid_len = htons(10),
+ .duid_type = htons(3),
+ .hardware_type = htons(1),
+ .msg_type = htons(DHCPV6_OPT_RECONF_MSG),
+ .msg_len = htons(1),
+ .msg_id = DHCPV6_MSG_RENEW,
+ .auth = {htons(DHCPV6_OPT_AUTH),
+ htons(sizeof(reconf_msg.auth) - 4), 3, 1, 0,
+ {htonl(time(NULL)), htonl(++serial)}, 2, {0}},
+ .clid_type = htons(DHCPV6_OPT_CLIENTID),
+ .clid_len = htons(assign->clid_len),
+ .clid_data = {0},
+ };
+
+ odhcpd_get_mac(iface, reconf_msg.mac);
+ memcpy(reconf_msg.clid_data, assign->clid_data, assign->clid_len);
+ struct iovec iov = {&reconf_msg, sizeof(reconf_msg) - 128 + assign->clid_len};
+
+ md5_ctx_t md5;
+ uint8_t secretbytes[16];
+ memcpy(secretbytes, assign->key, sizeof(secretbytes));
+
+ for (size_t i = 0; i < sizeof(secretbytes); ++i)
+ secretbytes[i] ^= 0x36;
+
+ md5_begin(&md5);
+ md5_hash(secretbytes, sizeof(secretbytes), &md5);
+ md5_hash(iov.iov_base, iov.iov_len, &md5);
+ md5_end(reconf_msg.auth.key, &md5);
+
+ for (size_t i = 0; i < sizeof(secretbytes); ++i) {
+ secretbytes[i] ^= 0x36;
+ secretbytes[i] ^= 0x5c;
+ }
+
+ md5_begin(&md5);
+ md5_hash(secretbytes, sizeof(secretbytes), &md5);
+ md5_hash(reconf_msg.auth.key, 16, &md5);
+ md5_end(reconf_msg.auth.key, &md5);
+
+ return odhcpd_send(socket_fd, &assign->peer, &iov, 1, iface);
+}
+
+
+void dhcpv6_write_statefile(void)
+{
+ if (config.dhcp_statefile) {
+ time_t now = odhcpd_time(), wall_time = time(NULL);
+ int fd = open(config.dhcp_statefile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
+ if (fd < 0)
+ return;
+
+ lockf(fd, F_LOCK, 0);
+ ftruncate(fd, 0);
+
+ FILE *fp = fdopen(fd, "w");
+ if (!fp) {
+ close(fd);
+ return;
+ }
+
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head) {
+ if (iface->dhcpv6 != RELAYD_SERVER && iface->dhcpv4 != RELAYD_SERVER)
+ continue;
+
+ if (iface->dhcpv6 == RELAYD_SERVER) {
+ struct dhcpv6_assignment *c;
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (c->clid_len == 0)
+ continue;
+
+ char ipbuf[INET6_ADDRSTRLEN];
+ char leasebuf[512];
+ char duidbuf[264];
+ odhcpd_hexlify(duidbuf, c->clid_data, c->clid_len);
+
+ // iface DUID iaid hostname lifetime assigned length [addrs...]
+ int l = snprintf(leasebuf, sizeof(leasebuf), "# %s %s %x %s %u %x %u ",
+ iface->ifname, duidbuf, ntohl(c->iaid),
+ (c->hostname ? c->hostname : "-"),
+ (unsigned)(c->valid_until > now ?
+ (c->valid_until - now + wall_time) : 0),
+ c->assigned, (unsigned)c->length);
+
+ struct in6_addr addr;
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ if (iface->ia_addr[i].prefix > 64)
+ continue;
+
+ addr = iface->ia_addr[i].addr;
+ if (c->length == 128)
+ addr.s6_addr32[3] = htonl(c->assigned);
+ else
+ addr.s6_addr32[1] |= htonl(c->assigned);
+ inet_ntop(AF_INET6, &addr, ipbuf, sizeof(ipbuf) - 1);
+
+ if (c->length == 128 && c->hostname && i == 0)
+ fprintf(fp, "%s\t%s\n", ipbuf, c->hostname);
+
+ l += snprintf(leasebuf + l, sizeof(leasebuf) - l, "%s/%hhu ", ipbuf, c->length);
+ }
+ leasebuf[l - 1] = '\n';
+ fwrite(leasebuf, 1, l, fp);
+ }
+ }
+
+ if (iface->dhcpv4 == RELAYD_SERVER) {
+ struct dhcpv4_assignment *c;
+ list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
+ char ipbuf[INET6_ADDRSTRLEN];
+ char leasebuf[512];
+ char duidbuf[16];
+ odhcpd_hexlify(duidbuf, c->hwaddr, sizeof(c->hwaddr));
+
+ // iface DUID iaid hostname lifetime assigned length [addrs...]
+ int l = snprintf(leasebuf, sizeof(leasebuf), "# %s %s ipv4 %s %u %x 32 ",
+ iface->ifname, duidbuf,
+ (c->hostname ? c->hostname : "-"),
+ (unsigned)(c->valid_until > now ?
+ (c->valid_until - now + wall_time) : 0),
+ c->addr);
+
+ struct in_addr addr = {htonl(c->addr)};
+ inet_ntop(AF_INET, &addr, ipbuf, sizeof(ipbuf) - 1);
+
+ if (c->hostname[0])
+ fprintf(fp, "%s\t%s\n", ipbuf, c->hostname);
+
+ l += snprintf(leasebuf + l, sizeof(leasebuf) - l, "%s/32 ", ipbuf);
+ leasebuf[l - 1] = '\n';
+ fwrite(leasebuf, 1, l, fp);
+ }
+ }
+ }
+
+ fclose(fp);
+ }
+
+ if (config.dhcp_cb) {
+ char *argv[2] = {config.dhcp_cb, NULL};
+ if (!vfork()) {
+ execv(argv[0], argv);
+ _exit(128);
+ }
+ }
+}
+
+
+static void apply_lease(struct interface *iface, struct dhcpv6_assignment *a, bool add)
+{
+ if (a->length > 64)
+ return;
+
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ struct in6_addr prefix = iface->ia_addr[i].addr;
+ prefix.s6_addr32[1] |= htonl(a->assigned);
+ odhcpd_setup_route(&prefix, a->length, iface, &a->peer.sin6_addr, add);
+ }
+}
+
+
+static bool assign_pd(struct interface *iface, struct dhcpv6_assignment *assign)
+{
+ struct dhcpv6_assignment *c;
+ if (iface->ia_addr_len < 1)
+ return false;
+
+ // Try honoring the hint first
+ uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1;
+ if (assign->assigned) {
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (c->length == 128)
+ continue;
+
+ if (assign->assigned >= current && assign->assigned + asize < c->assigned) {
+ list_add_tail(&assign->head, &c->head);
+ apply_lease(iface, assign, true);
+ return true;
+ }
+
+ if (c->assigned != 0)
+ current = (c->assigned + (1 << (64 - c->length)));
+ }
+ }
+
+ // Fallback to a variable assignment
+ current = 1;
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (c->length == 128)
+ continue;
+
+ current = (current + asize) & (~asize);
+ if (current + asize < c->assigned) {
+ assign->assigned = current;
+ list_add_tail(&assign->head, &c->head);
+ apply_lease(iface, assign, true);
+ return true;
+ }
+
+ if (c->assigned != 0)
+ current = (c->assigned + (1 << (64 - c->length)));
+ }
+
+ return false;
+}
+
+
+static bool assign_na(struct interface *iface, struct dhcpv6_assignment *assign)
+{
+ if (iface->ia_addr_len < 1)
+ return false;
+
+ // Seed RNG with checksum of DUID
+ uint32_t seed = 0;
+ for (size_t i = 0; i < assign->clid_len; ++i)
+ seed += assign->clid_data[i];
+ srand(seed);
+
+ // Try to assign up to 100x
+ for (size_t i = 0; i < 100; ++i) {
+ uint32_t try;
+ do try = ((uint32_t)rand()) % 0x0fff; while (try < 0x100);
+
+ struct dhcpv6_assignment *c;
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (c->assigned > try || c->length != 128) {
+ assign->assigned = try;
+ list_add_tail(&assign->head, &c->head);
+ return true;
+ } else if (c->assigned == try) {
+ break;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+static int prefixcmp(const void *va, const void *vb)
+{
+ const struct odhcpd_ipaddr *a = va, *b = vb;
+ uint32_t a_pref = ((a->addr.s6_addr[0] & 0xfe) != 0xfc) ? a->preferred : 1;
+ uint32_t b_pref = ((b->addr.s6_addr[0] & 0xfe) != 0xfc) ? b->preferred : 1;
+ return (a_pref < b_pref) ? 1 : (a_pref > b_pref) ? -1 : 0;
+}
+
+
+static void update(struct interface *iface)
+{
+ struct odhcpd_ipaddr addr[8];
+ memset(addr, 0, sizeof(addr));
+ int len = odhcpd_get_interface_addresses(iface->ifindex, addr, 8);
+
+ if (len < 0)
+ return;
+
+ qsort(addr, len, sizeof(*addr), prefixcmp);
+
+ time_t now = odhcpd_time();
+ int minprefix = -1;
+
+ for (int i = 0; i < len; ++i) {
+ if (addr[i].prefix > minprefix)
+ minprefix = addr[i].prefix;
+
+ addr[i].addr.s6_addr32[2] = 0;
+ addr[i].addr.s6_addr32[3] = 0;
+
+ if (addr[i].preferred < UINT32_MAX - now)
+ addr[i].preferred += now;
+
+ if (addr[i].valid < UINT32_MAX - now)
+ addr[i].valid += now;
+ }
+
+ struct dhcpv6_assignment *border = list_last_entry(&iface->ia_assignments, struct dhcpv6_assignment, head);
+ border->assigned = 1 << (64 - minprefix);
+
+ bool change = len != (int)iface->ia_addr_len;
+ for (int i = 0; !change && i < len; ++i)
+ if (addr[i].addr.s6_addr32[0] != iface->ia_addr[i].addr.s6_addr32[0] ||
+ addr[i].addr.s6_addr32[1] != iface->ia_addr[i].addr.s6_addr32[1] ||
+ (addr[i].preferred > 0) != (iface->ia_addr[i].preferred > 0) ||
+ (addr[i].valid > now + 7200) != (iface->ia_addr[i].valid > now + 7200))
+ change = true;
+
+ if (change) {
+ struct dhcpv6_assignment *c;
+ list_for_each_entry(c, &iface->ia_assignments, head)
+ if (c != border)
+ apply_lease(iface, c, false);
+ }
+
+ memcpy(iface->ia_addr, addr, len * sizeof(*addr));
+ iface->ia_addr_len = len;
+
+ if (change) { // Addresses / prefixes have changed
+ struct list_head reassign = LIST_HEAD_INIT(reassign);
+ struct dhcpv6_assignment *c, *d;
+ list_for_each_entry_safe(c, d, &iface->ia_assignments, head) {
+ if (c->clid_len == 0 || c->valid_until < now)
+ continue;
+
+ if (c->length < 128 && c->assigned >= border->assigned && c != border)
+ list_move(&c->head, &reassign);
+ else if (c != border)
+ apply_lease(iface, c, true);
+
+ if (c->accept_reconf && c->reconf_cnt == 0) {
+ c->reconf_cnt = 1;
+ c->reconf_sent = now;
+ send_reconf(iface, c);
+
+ // Leave all other assignments of that client alone
+ struct dhcpv6_assignment *a;
+ list_for_each_entry(a, &iface->ia_assignments, head)
+ if (a != c && a->clid_len == c->clid_len &&
+ !memcmp(a->clid_data, c->clid_data, a->clid_len))
+ c->reconf_cnt = INT_MAX;
+ }
+ }
+
+ while (!list_empty(&reassign)) {
+ c = list_first_entry(&reassign, struct dhcpv6_assignment, head);
+ list_del(&c->head);
+ if (!assign_pd(iface, c)) {
+ c->assigned = 0;
+ list_add(&c->head, &iface->ia_assignments);
+ }
+ }
+
+ dhcpv6_write_statefile();
+ }
+}
+
+
+static void reconf_timer(struct uloop_timeout *event)
+{
+ time_t now = odhcpd_time();
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head) {
+ if (iface->dhcpv6 != RELAYD_SERVER || iface->ia_assignments.next == NULL)
+ continue;
+
+ struct dhcpv6_assignment *a, *n;
+ list_for_each_entry_safe(a, n, &iface->ia_assignments, head) {
+ if (a->valid_until < now) {
+ if ((a->length < 128 && a->clid_len > 0) ||
+ (a->length == 128 && a->clid_len == 0)) {
+ list_del(&a->head);
+ free(a->hostname);
+ free(a);
+ }
+ } else if (a->reconf_cnt > 0 && a->reconf_cnt < 8 &&
+ now > a->reconf_sent + (1 << a->reconf_cnt)) {
+ ++a->reconf_cnt;
+ a->reconf_sent = now;
+ send_reconf(iface, a);
+ }
+ }
+
+ if (iface->ia_reconf) {
+ update(iface);
+ iface->ia_reconf = false;
+ }
+ }
+
+ uloop_timeout_set(event, 2000);
+}
+
+
+static size_t append_reply(uint8_t *buf, size_t buflen, uint16_t status,
+ const struct dhcpv6_ia_hdr *ia, struct dhcpv6_assignment *a,
+ struct interface *iface, bool request)
+{
+ if (buflen < sizeof(*ia) + sizeof(struct dhcpv6_ia_prefix))
+ return 0;
+
+ struct dhcpv6_ia_hdr out = {ia->type, 0, ia->iaid, 0, 0};
+ size_t datalen = sizeof(out);
+ time_t now = odhcpd_time();
+
+ if (status) {
+ struct __attribute__((packed)) {
+ uint16_t type;
+ uint16_t len;
+ uint16_t value;
+ } stat = {htons(DHCPV6_OPT_STATUS), htons(sizeof(stat) - 4),
+ htons(status)};
+
+ memcpy(buf + datalen, &stat, sizeof(stat));
+ datalen += sizeof(stat);
+ } else {
+ if (a) {
+ uint32_t pref = 3600;
+ uint32_t valid = 3600;
+ bool have_non_ula = false;
+ for (size_t i = 0; i < iface->ia_addr_len; ++i)
+ if ((iface->ia_addr[i].addr.s6_addr[0] & 0xfe) != 0xfc)
+ have_non_ula = true;
+
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ uint32_t prefix_pref = iface->ia_addr[i].preferred - now;
+ uint32_t prefix_valid = iface->ia_addr[i].valid - now;
+
+ if (iface->ia_addr[i].prefix > 64 ||
+ iface->ia_addr[i].preferred <= (uint32_t)now)
+ continue;
+
+ // ULA-deprecation compatibility workaround
+ if ((iface->ia_addr[i].addr.s6_addr[0] & 0xfe) == 0xfc &&
+ a->length == 128 && have_non_ula &&
+ iface->deprecate_ula_if_public_avail)
+ continue;
+
+ if (prefix_pref > 86400)
+ prefix_pref = 86400;
+
+ if (prefix_valid > 86400)
+ prefix_valid = 86400;
+
+ if (a->length < 128) {
+ struct dhcpv6_ia_prefix p = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(sizeof(p) - 4),
+ .preferred = htonl(prefix_pref),
+ .valid = htonl(prefix_valid),
+ .prefix = a->length,
+ .addr = iface->ia_addr[i].addr
+ };
+ p.addr.s6_addr32[1] |= htonl(a->assigned);
+
+ if (datalen + sizeof(p) > buflen || a->assigned == 0)
+ continue;
+
+ memcpy(buf + datalen, &p, sizeof(p));
+ datalen += sizeof(p);
+ } else {
+ struct dhcpv6_ia_addr n = {
+ .type = htons(DHCPV6_OPT_IA_ADDR),
+ .len = htons(sizeof(n) - 4),
+ .addr = iface->ia_addr[i].addr,
+ .preferred = htonl(prefix_pref),
+ .valid = htonl(prefix_valid)
+ };
+ n.addr.s6_addr32[3] = htonl(a->assigned);
+
+ if (datalen + sizeof(n) > buflen || a->assigned == 0)
+ continue;
+
+ memcpy(buf + datalen, &n, sizeof(n));
+ datalen += sizeof(n);
+ }
+
+ // Calculate T1 / T2 based on non-deprecated addresses
+ if (prefix_pref > 0) {
+ if (prefix_pref < pref)
+ pref = prefix_pref;
+
+ if (prefix_valid < valid)
+ valid = prefix_valid;
+ }
+ }
+
+ a->valid_until = valid + now;
+ out.t1 = htonl(pref * 5 / 10);
+ out.t2 = htonl(pref * 8 / 10);
+
+ if (!out.t1)
+ out.t1 = htonl(1);
+
+ if (!out.t2)
+ out.t2 = htonl(1);
+ }
+
+ if (!request) {
+ uint8_t *odata, *end = ((uint8_t*)ia) + htons(ia->len) + 4;
+ uint16_t otype, olen;
+ dhcpv6_for_each_option((uint8_t*)&ia[1], end, otype, olen, odata) {
+ struct dhcpv6_ia_prefix *p = (struct dhcpv6_ia_prefix*)&odata[-4];
+ struct dhcpv6_ia_addr *n = (struct dhcpv6_ia_addr*)&odata[-4];
+ if ((otype != DHCPV6_OPT_IA_PREFIX || olen < sizeof(*p) - 4) &&
+ (otype != DHCPV6_OPT_IA_ADDR || olen < sizeof(*n) - 4))
+ continue;
+
+ bool found = false;
+ if (a) {
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ if (iface->ia_addr[i].prefix > 64 ||
+ iface->ia_addr[i].preferred <= (uint32_t)now)
+ continue;
+
+ struct in6_addr addr = iface->ia_addr[i].addr;
+ if (ia->type == htons(DHCPV6_OPT_IA_PD)) {
+ addr.s6_addr32[1] |= htonl(a->assigned);
+
+ if (IN6_ARE_ADDR_EQUAL(&p->addr, &addr) &&
+ p->prefix == a->length)
+ found = true;
+ } else {
+ addr.s6_addr32[3] = htonl(a->assigned);
+
+ if (IN6_ARE_ADDR_EQUAL(&n->addr, &addr))
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ if (otype == DHCPV6_OPT_IA_PREFIX) {
+ struct dhcpv6_ia_prefix inv = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(sizeof(inv) - 4),
+ .preferred = 0,
+ .valid = 0,
+ .prefix = p->prefix,
+ .addr = p->addr
+ };
+
+ if (datalen + sizeof(inv) > buflen)
+ continue;
+
+ memcpy(buf + datalen, &inv, sizeof(inv));
+ datalen += sizeof(inv);
+ } else {
+ struct dhcpv6_ia_addr inv = {
+ .type = htons(DHCPV6_OPT_IA_ADDR),
+ .len = htons(sizeof(inv) - 4),
+ .addr = n->addr,
+ .preferred = 0,
+ .valid = 0
+ };
+
+ if (datalen + sizeof(inv) > buflen)
+ continue;
+
+ memcpy(buf + datalen, &inv, sizeof(inv));
+ datalen += sizeof(inv);
+ }
+ }
+ }
+ }
+ }
+
+ out.len = htons(datalen - 4);
+ memcpy(buf, &out, sizeof(out));
+ return datalen;
+}
+
+
+size_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface,
+ const struct sockaddr_in6 *addr, const void *data, const uint8_t *end)
+{
+ time_t now = odhcpd_time();
+ size_t response_len = 0;
+ const struct dhcpv6_client_header *hdr = data;
+ uint8_t *start = (uint8_t*)&hdr[1], *odata;
+ uint16_t otype, olen;
+
+ // Find and parse client-id and hostname
+ bool accept_reconf = false;
+ uint8_t *clid_data = NULL, clid_len = 0, mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ char hostname[256];
+ size_t hostname_len = 0;
+ dhcpv6_for_each_option(start, end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_CLIENTID) {
+ clid_data = odata;
+ clid_len = olen;
+
+ if (olen == 14 && odata[0] == 0 && odata[1] == 1)
+ memcpy(mac, &odata[8], sizeof(mac));
+ else if (olen == 10 && odata[0] == 0 && odata[1] == 3)
+ memcpy(mac, &odata[4], sizeof(mac));
+ } else if (otype == DHCPV6_OPT_FQDN && olen >= 2 && olen <= 255) {
+ uint8_t fqdn_buf[256];
+ memcpy(fqdn_buf, odata, olen);
+ fqdn_buf[olen++] = 0;
+
+ if (dn_expand(&fqdn_buf[1], &fqdn_buf[olen], &fqdn_buf[1], hostname, sizeof(hostname)) > 0)
+ hostname_len = strcspn(hostname, ".");
+ } else if (otype == DHCPV6_OPT_RECONF_ACCEPT) {
+ accept_reconf = true;
+ }
+ }
+
+ if (!clid_data || !clid_len || clid_len > 130)
+ goto out;
+
+ update(iface);
+ bool update_state = false;
+
+ struct dhcpv6_assignment *first = NULL;
+ dhcpv6_for_each_option(start, end, otype, olen, odata) {
+ bool is_pd = (otype == DHCPV6_OPT_IA_PD);
+ bool is_na = (otype == DHCPV6_OPT_IA_NA);
+ if (!is_pd && !is_na)
+ continue;
+
+ struct dhcpv6_ia_hdr *ia = (struct dhcpv6_ia_hdr*)&odata[-4];
+ size_t ia_response_len = 0;
+ uint8_t reqlen = (is_pd) ? 62 : 128;
+ uint32_t reqhint = 0;
+
+ // Parse request hint for IA-PD
+ if (is_pd) {
+ uint8_t *sdata;
+ uint16_t stype, slen;
+ dhcpv6_for_each_option(&ia[1], odata + olen, stype, slen, sdata) {
+ if (stype == DHCPV6_OPT_IA_PREFIX && slen >= sizeof(struct dhcpv6_ia_prefix) - 4) {
+ struct dhcpv6_ia_prefix *p = (struct dhcpv6_ia_prefix*)&sdata[-4];
+ if (p->prefix) {
+ reqlen = p->prefix;
+ reqhint = ntohl(p->addr.s6_addr32[1]);
+ if (reqlen > 32 && reqlen <= 64)
+ reqhint &= (1U << (64 - reqlen)) - 1;
+ }
+ break;
+ }
+ }
+
+ if (reqlen > 64)
+ reqlen = 64;
+ }
+
+ // Find assignment
+ struct dhcpv6_assignment *c, *a = NULL;
+ list_for_each_entry(c, &iface->ia_assignments, head) {
+ if (((c->clid_len == clid_len && !memcmp(c->clid_data, clid_data, clid_len)) ||
+ (c->clid_len >= clid_len && !c->clid_data[0] && !c->clid_data[1]
+ && !memcmp(c->mac, mac, sizeof(mac)))) &&
+ (c->iaid == ia->iaid || c->valid_until < now) &&
+ ((is_pd && c->length <= 64) || (is_na && c->length == 128))) {
+ a = c;
+
+ // Reset state
+ apply_lease(iface, a, false);
+ memcpy(a->clid_data, clid_data, clid_len);
+ a->clid_len = clid_len;
+ a->iaid = ia->iaid;
+ a->peer = *addr;
+ a->reconf_cnt = 0;
+ a->reconf_sent = 0;
+ break;
+ }
+ }
+
+ // Generic message handling
+ uint16_t status = DHCPV6_STATUS_OK;
+ if (hdr->msg_type == DHCPV6_MSG_SOLICIT || hdr->msg_type == DHCPV6_MSG_REQUEST) {
+ bool assigned = !!a;
+
+ if (!a && !iface->no_dynamic_dhcp) { // Create new binding
+ a = calloc(1, sizeof(*a) + clid_len);
+ a->clid_len = clid_len;
+ a->iaid = ia->iaid;
+ a->length = reqlen;
+ a->peer = *addr;
+ a->assigned = reqhint;
+ if (first)
+ memcpy(a->key, first->key, sizeof(a->key));
+ else
+ odhcpd_urandom(a->key, sizeof(a->key));
+ memcpy(a->clid_data, clid_data, clid_len);
+
+ if (is_pd)
+ while (!(assigned = assign_pd(iface, a)) && ++a->length <= 64);
+ else
+ assigned = assign_na(iface, a);
+ }
+
+ if (!assigned || iface->ia_addr_len == 0) { // Set error status
+ status = (is_pd) ? DHCPV6_STATUS_NOPREFIXAVAIL : DHCPV6_STATUS_NOADDRSAVAIL;
+ } else if (assigned && !first) { //
+ size_t handshake_len = 4;
+ buf[0] = 0;
+ buf[1] = DHCPV6_OPT_RECONF_ACCEPT;
+ buf[2] = 0;
+ buf[3] = 0;
+
+ if (hdr->msg_type == DHCPV6_MSG_REQUEST) {
+ struct dhcpv6_auth_reconfigure auth = {
+ htons(DHCPV6_OPT_AUTH),
+ htons(sizeof(auth) - 4),
+ 3, 1, 0,
+ {htonl(time(NULL)), htonl(++serial)},
+ 1,
+ {0}
+ };
+ memcpy(auth.key, a->key, sizeof(a->key));
+ memcpy(buf + handshake_len, &auth, sizeof(auth));
+ handshake_len += sizeof(auth);
+ }
+
+ buf += handshake_len;
+ buflen -= handshake_len;
+ response_len += handshake_len;
+
+ first = a;
+ }
+
+ ia_response_len = append_reply(buf, buflen, status, ia, a, iface, true);
+
+ // Was only a solicitation: mark binding for removal
+ if (assigned && hdr->msg_type == DHCPV6_MSG_SOLICIT) {
+ a->valid_until = 0;
+ } else if (assigned && hdr->msg_type == DHCPV6_MSG_REQUEST) {
+ if (hostname_len > 0) {
+ a->hostname = realloc(a->hostname, hostname_len + 1);
+ memcpy(a->hostname, hostname, hostname_len);
+ a->hostname[hostname_len] = 0;
+ }
+ a->accept_reconf = accept_reconf;
+ apply_lease(iface, a, true);
+ update_state = true;
+ } else if (!assigned && a) { // Cleanup failed assignment
+ free(a->hostname);
+ free(a);
+ }
+ } else if (hdr->msg_type == DHCPV6_MSG_RENEW ||
+ hdr->msg_type == DHCPV6_MSG_RELEASE ||
+ hdr->msg_type == DHCPV6_MSG_REBIND ||
+ hdr->msg_type == DHCPV6_MSG_DECLINE) {
+ if (!a && hdr->msg_type != DHCPV6_MSG_REBIND) {
+ status = DHCPV6_STATUS_NOBINDING;
+ ia_response_len = append_reply(buf, buflen, status, ia, a, iface, false);
+ } else if (hdr->msg_type == DHCPV6_MSG_RENEW ||
+ hdr->msg_type == DHCPV6_MSG_REBIND) {
+ ia_response_len = append_reply(buf, buflen, status, ia, a, iface, false);
+ if (a)
+ apply_lease(iface, a, true);
+ } else if (hdr->msg_type == DHCPV6_MSG_RELEASE) {
+ a->valid_until = 0;
+ apply_lease(iface, a, false);
+ update_state = true;
+ } else if (hdr->msg_type == DHCPV6_MSG_DECLINE && a->length == 128) {
+ a->clid_len = 0;
+ a->valid_until = now + 3600; // Block address for 1h
+ update_state = true;
+ }
+ } else if (hdr->msg_type == DHCPV6_MSG_CONFIRM) {
+ // Always send NOTONLINK for CONFIRM so that clients restart connection
+ status = DHCPV6_STATUS_NOTONLINK;
+ ia_response_len = append_reply(buf, buflen, status, ia, a, iface, true);
+ }
+
+ buf += ia_response_len;
+ buflen -= ia_response_len;
+ response_len += ia_response_len;
+ }
+
+ if (hdr->msg_type == DHCPV6_MSG_RELEASE && response_len + 6 < buflen) {
+ buf[0] = 0;
+ buf[1] = DHCPV6_OPT_STATUS;
+ buf[2] = 0;
+ buf[3] = 2;
+ buf[4] = 0;
+ buf[5] = DHCPV6_STATUS_OK;
+ response_len += 6;
+ }
+
+ if (update_state)
+ dhcpv6_write_statefile();
+
+out:
+ return response_len;
+}
diff --git a/src/dhcpv6.c b/src/dhcpv6.c
new file mode 100644
index 0000000..9515f40
--- /dev/null
+++ b/src/dhcpv6.c
@@ -0,0 +1,452 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <resolv.h>
+#include <sys/timerfd.h>
+
+#include "odhcpd.h"
+#include "dhcpv6.h"
+
+
+static void relay_client_request(struct sockaddr_in6 *source,
+ const void *data, size_t len, struct interface *iface);
+static void relay_server_response(uint8_t *data, size_t len);
+
+static void handle_dhcpv6(void *addr, void *data, size_t len,
+ struct interface *iface);
+static void handle_client_request(void *addr, void *data, size_t len,
+ struct interface *iface);
+
+static struct odhcpd_event dhcpv6_event = {{.fd = -1}, handle_dhcpv6};
+
+
+
+// Create socket and register events
+int init_dhcpv6(void)
+{
+ int sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+
+ // Basic IPv6 configuration
+ int val = 1;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+ setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val));
+
+ val = DHCPV6_HOP_COUNT_LIMIT;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val));
+
+ val = 0;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val));
+
+ struct sockaddr_in6 bind_addr = {AF_INET6, htons(DHCPV6_SERVER_PORT),
+ 0, IN6ADDR_ANY_INIT, 0};
+
+ if (bind(sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr))) {
+ syslog(LOG_ERR, "Failed to open DHCPv6 server socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ dhcpv6_event.uloop.fd = sock;
+ odhcpd_register(&dhcpv6_event);
+
+ dhcpv6_ia_init(dhcpv6_event.uloop.fd);
+
+ return 0;
+}
+
+
+int setup_dhcpv6_interface(struct interface *iface, bool enable)
+{
+ // Configure multicast settings
+ struct ipv6_mreq relay = {ALL_DHCPV6_RELAYS, iface->ifindex};
+ struct ipv6_mreq server = {ALL_DHCPV6_SERVERS, iface->ifindex};
+
+ setsockopt(dhcpv6_event.uloop.fd, IPPROTO_IPV6,
+ IPV6_DROP_MEMBERSHIP, &relay, sizeof(relay));
+ setsockopt(dhcpv6_event.uloop.fd, IPPROTO_IPV6,
+ IPV6_DROP_MEMBERSHIP, &server, sizeof(server));
+
+ if (enable && iface->dhcpv6 && !iface->master) {
+ setsockopt(dhcpv6_event.uloop.fd, IPPROTO_IPV6,
+ IPV6_ADD_MEMBERSHIP, &relay, sizeof(relay));
+
+ if (iface->dhcpv6 == RELAYD_SERVER)
+ setsockopt(dhcpv6_event.uloop.fd, IPPROTO_IPV6,
+ IPV6_ADD_MEMBERSHIP, &server, sizeof(server));
+ }
+
+ setup_dhcpv6_ia_interface(iface, enable);
+ return 0;
+}
+
+
+static void handle_nested_message(uint8_t *data, size_t len,
+ uint8_t **opts, uint8_t **end, struct iovec iov[6])
+{
+ struct dhcpv6_relay_header *hdr = (struct dhcpv6_relay_header*)data;
+ if (iov[0].iov_base == NULL) {
+ iov[0].iov_base = data;
+ iov[0].iov_len = len;
+ }
+
+ if (len < sizeof(struct dhcpv6_client_header))
+ return;
+
+ if (hdr->msg_type != DHCPV6_MSG_RELAY_FORW) {
+ iov[0].iov_len = data - (uint8_t*)iov[0].iov_base;
+ struct dhcpv6_client_header *hdr = (void*)data;
+ *opts = (uint8_t*)&hdr[1];
+ *end = data + len;
+ return;
+ }
+
+ uint16_t otype, olen;
+ uint8_t *odata;
+ dhcpv6_for_each_option(hdr->options, data + len, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_RELAY_MSG) {
+ iov[7].iov_base = odata + olen;
+ iov[7].iov_len = (((uint8_t*)iov[0].iov_base) + iov[0].iov_len)
+ - (odata + olen);
+ handle_nested_message(odata, olen, opts, end, iov);
+ return;
+ }
+ }
+}
+
+
+static void update_nested_message(uint8_t *data, size_t len, ssize_t pdiff)
+{
+ struct dhcpv6_relay_header *hdr = (struct dhcpv6_relay_header*)data;
+ if (hdr->msg_type != DHCPV6_MSG_RELAY_FORW)
+ return;
+
+ hdr->msg_type = DHCPV6_MSG_RELAY_REPL;
+
+ uint16_t otype, olen;
+ uint8_t *odata;
+ dhcpv6_for_each_option(hdr->options, data + len, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_RELAY_MSG) {
+ olen += pdiff;
+ odata[-2] = (olen >> 8) & 0xff;
+ odata[-1] = olen & 0xff;
+ update_nested_message(odata, olen - pdiff, pdiff);
+ return;
+ }
+ }
+}
+
+
+// Simple DHCPv6-server for information requests
+static void handle_client_request(void *addr, void *data, size_t len,
+ struct interface *iface)
+{
+ struct dhcpv6_client_header *hdr = data;
+ if (len < sizeof(*hdr))
+ return;
+
+ syslog(LOG_NOTICE, "Got DHCPv6 request");
+
+ // Construct reply message
+ struct __attribute__((packed)) {
+ uint8_t msg_type;
+ uint8_t tr_id[3];
+ uint16_t serverid_type;
+ uint16_t serverid_length;
+ uint16_t duid_type;
+ uint16_t hardware_type;
+ uint8_t mac[6];
+ uint16_t clientid_type;
+ uint16_t clientid_length;
+ uint8_t clientid_buf[130];
+ } dest = {
+ .msg_type = DHCPV6_MSG_REPLY,
+ .serverid_type = htons(DHCPV6_OPT_SERVERID),
+ .serverid_length = htons(10),
+ .duid_type = htons(3),
+ .hardware_type = htons(1),
+ .clientid_type = htons(DHCPV6_OPT_CLIENTID),
+ .clientid_buf = {0}
+ };
+ odhcpd_get_mac(iface, dest.mac);
+
+ struct __attribute__((packed)) {
+ uint16_t type;
+ uint16_t len;
+ uint16_t value;
+ } stat = {htons(DHCPV6_OPT_STATUS), htons(sizeof(stat) - 4),
+ htons(DHCPV6_STATUS_NOADDRSAVAIL)};
+
+ struct __attribute__((packed)) {
+ uint16_t type;
+ uint16_t len;
+ uint32_t value;
+ } refresh = {htons(DHCPV6_OPT_INFO_REFRESH), htons(sizeof(uint32_t)),
+ htonl(600)};
+
+ struct odhcpd_ipaddr ipaddr;
+ struct in6_addr *dns_addr = iface->dns;
+ size_t dns_cnt = iface->dns_cnt;
+
+ if (dns_cnt == 0 && odhcpd_get_interface_addresses(iface->ifindex, &ipaddr, 1) == 1) {
+ dns_addr = &ipaddr.addr;
+ dns_cnt = 1;
+ }
+
+ struct {
+ uint16_t type;
+ uint16_t len;
+ } dns = {htons(DHCPV6_OPT_DNS_SERVERS), htons(dns_cnt * sizeof(*dns_addr))};
+
+
+
+ // DNS Search options
+ uint8_t search_buf[256], *search_domain = iface->search;
+ size_t search_len = iface->search_len;
+
+ if (!search_domain && !res_init() && _res.dnsrch[0] && _res.dnsrch[0][0]) {
+ int len = dn_comp(_res.dnsrch[0], search_buf,
+ sizeof(search_buf), NULL, NULL);
+ if (len > 0) {
+ search_domain = search_buf;
+ search_len = len;
+ }
+ }
+
+ struct {
+ uint16_t type;
+ uint16_t len;
+ } search = {htons(DHCPV6_OPT_DNS_DOMAIN), htons(search_len)};
+
+
+
+ uint8_t pdbuf[512];
+ struct iovec iov[] = {{NULL, 0},
+ {&dest, (uint8_t*)&dest.clientid_type - (uint8_t*)&dest},
+ {&dns, (dns_cnt) ? sizeof(dns) : 0},
+ {dns_addr, dns_cnt * sizeof(*dns_addr)},
+ {&search, (search_len) ? sizeof(search) : 0},
+ {search_domain, search_len},
+ {pdbuf, 0},
+ {NULL, 0}};
+
+ uint8_t *opts = (uint8_t*)&hdr[1], *opts_end = (uint8_t*)data + len;
+ if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
+ handle_nested_message(data, len, &opts, &opts_end, iov);
+
+ memcpy(dest.tr_id, &opts[-3], sizeof(dest.tr_id));
+
+ if (opts[-4] == DHCPV6_MSG_ADVERTISE || opts[-4] == DHCPV6_MSG_REPLY || opts[-4] == DHCPV6_MSG_RELAY_REPL)
+ return;
+
+ if (opts[-4] == DHCPV6_MSG_SOLICIT) {
+ dest.msg_type = DHCPV6_MSG_ADVERTISE;
+ } else if (opts[-4] == DHCPV6_MSG_INFORMATION_REQUEST) {
+ iov[6].iov_base = &refresh;
+ iov[6].iov_len = sizeof(refresh);
+ }
+
+ // Go through options and find what we need
+ uint16_t otype, olen;
+ uint8_t *odata;
+ dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_CLIENTID && olen <= 130) {
+ dest.clientid_length = htons(olen);
+ memcpy(dest.clientid_buf, odata, olen);
+ iov[1].iov_len += 4 + olen;
+ } else if (otype == DHCPV6_OPT_SERVERID) {
+ if (olen != ntohs(dest.serverid_length) ||
+ memcmp(odata, &dest.duid_type, olen))
+ return; // Not for us
+ }
+ }
+
+ if (opts[-4] != DHCPV6_MSG_INFORMATION_REQUEST) {
+ iov[6].iov_len = dhcpv6_handle_ia(pdbuf, sizeof(pdbuf), iface, addr, &opts[-4], opts_end);
+ if (iov[6].iov_len == 0 && opts[-4] == DHCPV6_MSG_REBIND)
+ return;
+ }
+
+ if (iov[0].iov_len > 0) // Update length
+ update_nested_message(data, len, iov[1].iov_len + iov[2].iov_len +
+ iov[3].iov_len + iov[4].iov_len + iov[5].iov_len +
+ iov[6].iov_len - (4 + opts_end - opts));
+
+ odhcpd_send(dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface);
+}
+
+
+// Central DHCPv6-relay handler
+static void handle_dhcpv6(void *addr, void *data, size_t len,
+ struct interface *iface)
+{
+ if (iface->dhcpv6 == RELAYD_SERVER) {
+ handle_client_request(addr, data, len, iface);
+ } else if (iface->dhcpv6 == RELAYD_RELAY) {
+ if (iface->master)
+ relay_server_response(data, len);
+ else
+ relay_client_request(addr, data, len, iface);
+ }
+}
+
+
+// Relay server response (regular relay server handling)
+static void relay_server_response(uint8_t *data, size_t len)
+{
+ // Information we need to gather
+ uint8_t *payload_data = NULL;
+ size_t payload_len = 0;
+ int32_t ifaceidx = 0;
+ struct sockaddr_in6 target = {AF_INET6, htons(DHCPV6_CLIENT_PORT),
+ 0, IN6ADDR_ANY_INIT, 0};
+
+ syslog(LOG_NOTICE, "Got a DHCPv6-reply");
+
+ int otype, olen;
+ uint8_t *odata, *end = data + len;
+
+ // Relay DHCPv6 reply from server to client
+ struct dhcpv6_relay_header *h = (void*)data;
+ if (len < sizeof(*h) || h->msg_type != DHCPV6_MSG_RELAY_REPL)
+ return;
+
+ memcpy(&target.sin6_addr, &h->peer_address,
+ sizeof(struct in6_addr));
+
+ // Go through options and find what we need
+ dhcpv6_for_each_option(h->options, end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_INTERFACE_ID
+ && olen == sizeof(ifaceidx)) {
+ memcpy(&ifaceidx, odata, sizeof(ifaceidx));
+ } else if (otype == DHCPV6_OPT_RELAY_MSG) {
+ payload_data = odata;
+ payload_len = olen;
+ }
+ }
+
+ // Invalid interface-id or basic payload
+ struct interface *iface = odhcpd_get_interface_by_index(ifaceidx);
+ if (!iface || iface->master || !payload_data || payload_len < 4)
+ return;
+
+ bool is_authenticated = false;
+ struct in6_addr *dns_ptr = NULL;
+ size_t dns_count = 0;
+
+ // If the payload is relay-reply we have to send to the server port
+ if (payload_data[0] == DHCPV6_MSG_RELAY_REPL) {
+ target.sin6_port = htons(DHCPV6_SERVER_PORT);
+ } else { // Go through the payload data
+ struct dhcpv6_client_header *h = (void*)payload_data;
+ end = payload_data + payload_len;
+
+ dhcpv6_for_each_option(&h[1], end, otype, olen, odata) {
+ if (otype == DHCPV6_OPT_DNS_SERVERS && olen >= 16) {
+ dns_ptr = (struct in6_addr*)odata;
+ dns_count = olen / 16;
+ } else if (otype == DHCPV6_OPT_AUTH) {
+ is_authenticated = true;
+ }
+ }
+ }
+
+ // Rewrite DNS servers if requested
+ if (iface->always_rewrite_dns && dns_ptr && dns_count > 0) {
+ if (is_authenticated)
+ return; // Impossible to rewrite
+
+ struct odhcpd_ipaddr ip;
+ const struct in6_addr *rewrite = iface->dns;
+ size_t rewrite_cnt = iface->dns_cnt;
+
+ if (rewrite_cnt == 0) {
+ if (odhcpd_get_interface_addresses(iface->ifindex, &ip, 1) < 1)
+ return; // Unable to get interface address
+
+ rewrite = &ip.addr;
+ rewrite_cnt = 1;
+ }
+
+ // Copy over any other addresses
+ for (size_t i = 0; i < dns_count; ++i) {
+ size_t j = (i < rewrite_cnt) ? i : rewrite_cnt - 1;
+ memcpy(&dns_ptr[i], &rewrite[j], sizeof(*rewrite));
+ }
+ }
+
+ struct iovec iov = {payload_data, payload_len};
+ odhcpd_send(dhcpv6_event.uloop.fd, &target, &iov, 1, iface);
+}
+
+
+// Relay client request (regular DHCPv6-relay)
+static void relay_client_request(struct sockaddr_in6 *source,
+ const void *data, size_t len, struct interface *iface)
+{
+ struct interface *master = odhcpd_get_master_interface();
+ const struct dhcpv6_relay_header *h = data;
+ if (!master || master->dhcpv6 != RELAYD_RELAY ||
+ h->msg_type == DHCPV6_MSG_RELAY_REPL ||
+ h->msg_type == DHCPV6_MSG_RECONFIGURE ||
+ h->msg_type == DHCPV6_MSG_REPLY ||
+ h->msg_type == DHCPV6_MSG_ADVERTISE)
+ return; // Invalid message types for client
+
+ syslog(LOG_NOTICE, "Got a DHCPv6-request");
+
+ // Construct our forwarding envelope
+ struct dhcpv6_relay_forward_envelope hdr = {
+ .msg_type = DHCPV6_MSG_RELAY_FORW,
+ .hop_count = 0,
+ .interface_id_type = htons(DHCPV6_OPT_INTERFACE_ID),
+ .interface_id_len = htons(sizeof(uint32_t)),
+ .relay_message_type = htons(DHCPV6_OPT_RELAY_MSG),
+ .relay_message_len = htons(len),
+ };
+
+ if (h->msg_type == DHCPV6_MSG_RELAY_FORW) { // handle relay-forward
+ if (h->hop_count >= DHCPV6_HOP_COUNT_LIMIT)
+ return; // Invalid hop count
+ else
+ hdr.hop_count = h->hop_count + 1;
+ }
+
+ // use memcpy here as the destination fields are unaligned
+ uint32_t ifindex = iface->ifindex;
+ memcpy(&hdr.peer_address, &source->sin6_addr, sizeof(struct in6_addr));
+ memcpy(&hdr.interface_id_data, &ifindex, sizeof(ifindex));
+
+ // Detect public IP of slave interface to use as link-address
+ struct odhcpd_ipaddr ip;
+ if (odhcpd_get_interface_addresses(iface->ifindex, &ip, 1) < 1) {
+ // No suitable address! Is the slave not configured yet?
+ // Detect public IP of master interface and use it instead
+ // This is WRONG and probably violates the RFC. However
+ // otherwise we have a hen and egg problem because the
+ // slave-interface cannot be auto-configured.
+ if (odhcpd_get_interface_addresses(master->ifindex, &ip, 1) < 1)
+ return; // Could not obtain a suitable address
+ }
+ memcpy(&hdr.link_address, &ip.addr, sizeof(hdr.link_address));
+
+ struct sockaddr_in6 dhcpv6_servers = {AF_INET6,
+ htons(DHCPV6_SERVER_PORT), 0, ALL_DHCPV6_SERVERS, 0};
+ struct iovec iov[2] = {{&hdr, sizeof(hdr)}, {(void*)data, len}};
+ odhcpd_send(dhcpv6_event.uloop.fd, &dhcpv6_servers, iov, 2, master);
+}
diff --git a/src/dhcpv6.h b/src/dhcpv6.h
new file mode 100644
index 0000000..004c3cf
--- /dev/null
+++ b/src/dhcpv6.h
@@ -0,0 +1,162 @@
+/**
+ * Copyright (C) 2012 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ */
+#pragma once
+
+#define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02}}}
+
+#define ALL_DHCPV6_SERVERS {{{0xff, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03}}}
+
+#define DHCPV6_CLIENT_PORT 546
+#define DHCPV6_SERVER_PORT 547
+
+#define DHCPV6_MSG_SOLICIT 1
+#define DHCPV6_MSG_ADVERTISE 2
+#define DHCPV6_MSG_REQUEST 3
+#define DHCPV6_MSG_CONFIRM 4
+#define DHCPV6_MSG_RENEW 5
+#define DHCPV6_MSG_REBIND 6
+#define DHCPV6_MSG_REPLY 7
+#define DHCPV6_MSG_RELEASE 8
+#define DHCPV6_MSG_DECLINE 9
+#define DHCPV6_MSG_RECONFIGURE 10
+#define DHCPV6_MSG_INFORMATION_REQUEST 11
+#define DHCPV6_MSG_RELAY_FORW 12
+#define DHCPV6_MSG_RELAY_REPL 13
+
+#define DHCPV6_OPT_CLIENTID 1
+#define DHCPV6_OPT_SERVERID 2
+#define DHCPV6_OPT_IA_NA 3
+#define DHCPV6_OPT_IA_ADDR 5
+#define DHCPV6_OPT_STATUS 13
+#define DHCPV6_OPT_RELAY_MSG 9
+#define DHCPV6_OPT_AUTH 11
+#define DHCPV6_OPT_INTERFACE_ID 18
+#define DHCPV6_OPT_RECONF_MSG 19
+#define DHCPV6_OPT_RECONF_ACCEPT 20
+#define DHCPV6_OPT_DNS_SERVERS 23
+#define DHCPV6_OPT_DNS_DOMAIN 24
+#define DHCPV6_OPT_IA_PD 25
+#define DHCPV6_OPT_IA_PREFIX 26
+#define DHCPV6_OPT_INFO_REFRESH 32
+#define DHCPV6_OPT_FQDN 39
+
+#define DHCPV6_DUID_VENDOR 2
+
+#define DHCPV6_STATUS_OK 0
+#define DHCPV6_STATUS_NOADDRSAVAIL 2
+#define DHCPV6_STATUS_NOBINDING 3
+#define DHCPV6_STATUS_NOTONLINK 4
+#define DHCPV6_STATUS_NOPREFIXAVAIL 6
+
+// I just remembered I have an old one lying around...
+#define DHCPV6_ENT_NO 30462
+#define DHCPV6_ENT_TYPE 1
+
+
+#define DHCPV6_HOP_COUNT_LIMIT 32
+
+struct dhcpv6_client_header {
+ uint8_t msg_type;
+ uint8_t transaction_id[3];
+} __attribute__((packed));
+
+struct dhcpv6_relay_header {
+ uint8_t msg_type;
+ uint8_t hop_count;
+ struct in6_addr link_address;
+ struct in6_addr peer_address;
+ uint8_t options[];
+} __attribute__((packed));
+
+struct dhcpv6_relay_forward_envelope {
+ uint8_t msg_type;
+ uint8_t hop_count;
+ struct in6_addr link_address;
+ struct in6_addr peer_address;
+ uint16_t interface_id_type;
+ uint16_t interface_id_len;
+ uint32_t interface_id_data;
+ uint16_t relay_message_type;
+ uint16_t relay_message_len;
+} __attribute__((packed));
+
+struct dhcpv6_auth_reconfigure {
+ uint16_t type;
+ uint16_t len;
+ uint8_t protocol;
+ uint8_t algorithm;
+ uint8_t rdm;
+ uint32_t replay[2];
+ uint8_t reconf_type;
+ uint8_t key[16];
+} _packed;
+
+struct dhcpv6_ia_hdr {
+ uint16_t type;
+ uint16_t len;
+ uint32_t iaid;
+ uint32_t t1;
+ uint32_t t2;
+} _packed;
+
+struct dhcpv6_ia_prefix {
+ uint16_t type;
+ uint16_t len;
+ uint32_t preferred;
+ uint32_t valid;
+ uint8_t prefix;
+ struct in6_addr addr;
+} _packed;
+
+struct dhcpv6_ia_addr {
+ uint16_t type;
+ uint16_t len;
+ struct in6_addr addr;
+ uint32_t preferred;
+ uint32_t valid;
+} _packed;
+
+struct dhcpv6_assignment {
+ struct list_head head;
+ struct sockaddr_in6 peer;
+ time_t valid_until;
+ time_t reconf_sent;
+ int reconf_cnt;
+ char *hostname;
+ uint8_t key[16];
+ uint32_t assigned;
+ uint32_t iaid;
+ uint8_t mac[6];
+ uint8_t length; // length == 128 -> IA_NA, length <= 64 -> IA_PD
+ bool accept_reconf;
+ uint8_t clid_len;
+ uint8_t clid_data[];
+};
+
+
+
+#define dhcpv6_for_each_option(start, end, otype, olen, odata)\
+ for (uint8_t *_o = (uint8_t*)(start); _o + 4 <= (end) &&\
+ ((otype) = _o[0] << 8 | _o[1]) && ((odata) = (void*)&_o[4]) &&\
+ ((olen) = _o[2] << 8 | _o[3]) + (odata) <= (end); \
+ _o += 4 + (_o[2] << 8 | _o[3]))
+
+int dhcpv6_init_ia(struct interface *iface, int socket);
+size_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface,
+ const struct sockaddr_in6 *addr, const void *data, const uint8_t *end);
+int dhcpv6_ia_init(int dhcpv6_socket);
+int setup_dhcpv6_ia_interface(struct interface *iface, bool enable);
+void dhcpv6_write_statefile(void);
diff --git a/src/md5.c b/src/md5.c
new file mode 100644
index 0000000..ec24dd2
--- /dev/null
+++ b/src/md5.c
@@ -0,0 +1,242 @@
+/*
+ * md5.c - Compute MD5 checksum of strings according to the
+ * definition of MD5 in RFC 1321 from April 1992.
+ *
+ * Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
+ *
+ * Copyright (C) 1995-1999 Free Software Foundation, Inc.
+ * Copyright (C) 2001 Manuel Novoa III
+ * Copyright (C) 2003 Glenn L. McGrath
+ * Copyright (C) 2003 Erik Andersen
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <libubox/blob.h> /* TODO: better include for bswap_32 compat */
+#include "md5.h"
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SWAP_LE32(x) (x)
+#else
+#define SWAP_LE32(x) bswap_32(x)
+#endif
+
+/* Initialize structure containing state of computation.
+ * (RFC 1321, 3.3: Step 3)
+ */
+void md5_begin(md5_ctx_t *ctx)
+{
+ ctx->A = 0x67452301;
+ ctx->B = 0xefcdab89;
+ ctx->C = 0x98badcfe;
+ ctx->D = 0x10325476;
+
+ ctx->total = 0;
+ ctx->buflen = 0;
+}
+
+/* These are the four functions used in the four steps of the MD5 algorithm
+ * and defined in the RFC 1321. The first function is a little bit optimized
+ * (as found in Colin Plumbs public domain implementation).
+ * #define FF(b, c, d) ((b & c) | (~b & d))
+ */
+# define FF(b, c, d) (d ^ (b & (c ^ d)))
+# define FG(b, c, d) FF (d, b, c)
+# define FH(b, c, d) (b ^ c ^ d)
+# define FI(b, c, d) (c ^ (b | ~d))
+
+/* Hash a single block, 64 bytes long and 4-byte aligned. */
+static void md5_hash_block(const void *buffer, md5_ctx_t *ctx)
+{
+ uint32_t correct_words[16];
+ const uint32_t *words = buffer;
+
+ static const uint32_t C_array[] = {
+ /* round 1 */
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
+ /* round 2 */
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
+ 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
+ /* round 3 */
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ /* round 4 */
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+ };
+
+ static const char P_array[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */
+ 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */
+ 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */
+ 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */
+ };
+
+ static const char S_array[] = {
+ 7, 12, 17, 22,
+ 5, 9, 14, 20,
+ 4, 11, 16, 23,
+ 6, 10, 15, 21
+ };
+
+ uint32_t A = ctx->A;
+ uint32_t B = ctx->B;
+ uint32_t C = ctx->C;
+ uint32_t D = ctx->D;
+
+ uint32_t *cwp = correct_words;
+
+# define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
+
+ const uint32_t *pc;
+ const char *pp;
+ const char *ps;
+ int i;
+ uint32_t temp;
+
+ for (i = 0; i < 16; i++) {
+ cwp[i] = SWAP_LE32(words[i]);
+ }
+ words += 16;
+
+ pc = C_array;
+ pp = P_array;
+ ps = S_array;
+
+ for (i = 0; i < 16; i++) {
+ temp = A + FF(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+ CYCLIC(temp, ps[i & 3]);
+ temp += B;
+ A = D;
+ D = C;
+ C = B;
+ B = temp;
+ }
+
+ ps += 4;
+ for (i = 0; i < 16; i++) {
+ temp = A + FG(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+ CYCLIC(temp, ps[i & 3]);
+ temp += B;
+ A = D;
+ D = C;
+ C = B;
+ B = temp;
+ }
+ ps += 4;
+ for (i = 0; i < 16; i++) {
+ temp = A + FH(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+ CYCLIC(temp, ps[i & 3]);
+ temp += B;
+ A = D;
+ D = C;
+ C = B;
+ B = temp;
+ }
+ ps += 4;
+ for (i = 0; i < 16; i++) {
+ temp = A + FI(B, C, D) + cwp[(int) (*pp++)] + *pc++;
+ CYCLIC(temp, ps[i & 3]);
+ temp += B;
+ A = D;
+ D = C;
+ C = B;
+ B = temp;
+ }
+
+
+ ctx->A += A;
+ ctx->B += B;
+ ctx->C += C;
+ ctx->D += D;
+}
+
+/* Feed data through a temporary buffer to call md5_hash_aligned_block()
+ * with chunks of data that are 4-byte aligned and a multiple of 64 bytes.
+ * This function's internal buffer remembers previous data until it has 64
+ * bytes worth to pass on. Call md5_end() to flush this buffer. */
+
+void md5_hash(const void *buffer, size_t len, md5_ctx_t *ctx)
+{
+ char *buf = (char *)buffer;
+
+ /* RFC 1321 specifies the possible length of the file up to 2^64 bits,
+ * Here we only track the number of bytes. */
+
+ ctx->total += len;
+
+ // Process all input.
+
+ while (len) {
+ unsigned i = 64 - ctx->buflen;
+
+ // Copy data into aligned buffer.
+
+ if (i > len)
+ i = len;
+ memcpy(ctx->buffer + ctx->buflen, buf, i);
+ len -= i;
+ ctx->buflen += i;
+ buf += i;
+
+ // When buffer fills up, process it.
+
+ if (ctx->buflen == 64) {
+ md5_hash_block(ctx->buffer, ctx);
+ ctx->buflen = 0;
+ }
+ }
+}
+
+/* Process the remaining bytes in the buffer and put result from CTX
+ * in first 16 bytes following RESBUF. The result is always in little
+ * endian byte order, so that a byte-wise output yields to the wanted
+ * ASCII representation of the message digest.
+ *
+ * IMPORTANT: On some systems it is required that RESBUF is correctly
+ * aligned for a 32 bits value.
+ */
+void md5_end(void *resbuf, md5_ctx_t *ctx)
+{
+ char *buf = ctx->buffer;
+ int i;
+
+ /* Pad data to block size. */
+
+ buf[ctx->buflen++] = 0x80;
+ memset(buf + ctx->buflen, 0, 128 - ctx->buflen);
+
+ /* Put the 64-bit file length in *bits* at the end of the buffer. */
+ ctx->total <<= 3;
+ if (ctx->buflen > 56)
+ buf += 64;
+
+ for (i = 0; i < 8; i++)
+ buf[56 + i] = ctx->total >> (i*8);
+
+ /* Process last bytes. */
+ if (buf != ctx->buffer)
+ md5_hash_block(ctx->buffer, ctx);
+ md5_hash_block(buf, ctx);
+
+ /* Put result from CTX in first 16 bytes following RESBUF. The result is
+ * always in little endian byte order, so that a byte-wise output yields
+ * to the wanted ASCII representation of the message digest.
+ *
+ * IMPORTANT: On some systems it is required that RESBUF is correctly
+ * aligned for a 32 bits value.
+ */
+ ((uint32_t *) resbuf)[0] = SWAP_LE32(ctx->A);
+ ((uint32_t *) resbuf)[1] = SWAP_LE32(ctx->B);
+ ((uint32_t *) resbuf)[2] = SWAP_LE32(ctx->C);
+ ((uint32_t *) resbuf)[3] = SWAP_LE32(ctx->D);
+}
diff --git a/src/md5.h b/src/md5.h
new file mode 100644
index 0000000..fb79ae0
--- /dev/null
+++ b/src/md5.h
@@ -0,0 +1,17 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct md5_ctx {
+ uint32_t A;
+ uint32_t B;
+ uint32_t C;
+ uint32_t D;
+ uint64_t total;
+ uint32_t buflen;
+ char buffer[128];
+} md5_ctx_t;
+
+void md5_begin(md5_ctx_t *ctx);
+void md5_hash(const void *data, size_t length, md5_ctx_t *ctx);
+void md5_end(void *resbuf, md5_ctx_t *ctx);
diff --git a/src/ndp.c b/src/ndp.c
new file mode 100644
index 0000000..89bcd3c
--- /dev/null
+++ b/src/ndp.c
@@ -0,0 +1,532 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/ethernet.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <netpacket/packet.h>
+
+#include <linux/rtnetlink.h>
+#include <linux/filter.h>
+#include "router.h"
+#include "ndp.h"
+
+
+
+static void handle_solicit(void *addr, void *data, size_t len,
+ struct interface *iface);
+static void handle_rtnetlink(void *addr, void *data, size_t len,
+ struct interface *iface);
+static struct ndp_neighbor* find_neighbor(struct in6_addr *addr, bool strict);
+static void modify_neighbor(struct in6_addr *addr, struct interface *iface,
+ bool add);
+static ssize_t ping6(struct in6_addr *addr,
+ const struct interface *iface);
+
+static struct list_head neighbors = LIST_HEAD_INIT(neighbors);
+static size_t neighbor_count = 0;
+static uint32_t rtnl_seqid = 0;
+
+static int ping_socket = -1;
+static struct odhcpd_event ndp_event = {{.fd = -1}, handle_solicit};
+static struct odhcpd_event rtnl_event = {{.fd = -1}, handle_rtnetlink};
+
+
+// Filter ICMPv6 messages of type neighbor soliciation
+static struct sock_filter bpf[] = {
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) +
+ offsetof(struct icmp6_hdr, icmp6_type)),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
+ BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
+ BPF_STMT(BPF_RET | BPF_K, 0),
+};
+static const struct sock_fprog bpf_prog = {sizeof(bpf) / sizeof(*bpf), bpf};
+
+
+// Initialize NDP-proxy
+int init_ndp(void)
+{
+ // Setup netlink socket
+ if ((rtnl_event.uloop.fd = odhcpd_open_rtnl()) < 0)
+ return -1;
+
+ // Receive netlink neighbor and ip-address events
+ uint32_t group = RTNLGRP_IPV6_IFADDR;
+ setsockopt(rtnl_event.uloop.fd, SOL_NETLINK,
+ NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
+ group = RTNLGRP_IPV6_ROUTE;
+ setsockopt(rtnl_event.uloop.fd, SOL_NETLINK,
+ NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
+
+ // Synthesize initial address events
+ struct {
+ struct nlmsghdr nh;
+ struct ifaddrmsg ifa;
+ } req2 = {
+ {sizeof(req2), RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP,
+ ++rtnl_seqid, 0},
+ {.ifa_family = AF_INET6}
+ };
+ send(rtnl_event.uloop.fd, &req2, sizeof(req2), MSG_DONTWAIT);
+ odhcpd_register(&rtnl_event);
+
+
+ // Create socket for intercepting NDP
+ int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ htons(ETH_P_ALL)); // ETH_P_ALL for ingress + egress
+ if (sock < 0) {
+ syslog(LOG_ERR, "Unable to open packet socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER,
+ &bpf_prog, sizeof(bpf_prog))) {
+ syslog(LOG_ERR, "Failed to set BPF: %s", strerror(errno));
+ return -1;
+ }
+
+ ndp_event.uloop.fd = sock;
+ odhcpd_register(&ndp_event);
+
+ // Open ICMPv6 socket
+ ping_socket = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
+
+ int val = 2;
+ setsockopt(ping_socket, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val));
+
+ // This is required by RFC 4861
+ val = 255;
+ setsockopt(ping_socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val));
+ setsockopt(ping_socket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val));
+
+ // Filter all packages, we only want to send
+ struct icmp6_filter filt;
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ setsockopt(ping_socket, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt));
+
+
+ // Netlink socket, continued...
+ group = RTNLGRP_NEIGH;
+ setsockopt(rtnl_event.uloop.fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
+
+ // Synthesize initial neighbor events
+ struct {
+ struct nlmsghdr nh;
+ struct ndmsg ndm;
+ } req = {
+ {sizeof(req), RTM_GETNEIGH, NLM_F_REQUEST | NLM_F_DUMP,
+ ++rtnl_seqid, 0},
+ {.ndm_family = AF_INET6}
+ };
+ send(rtnl_event.uloop.fd, &req, sizeof(req), MSG_DONTWAIT);
+
+ return 0;
+}
+
+
+int setup_ndp_interface(struct interface *iface, bool enable)
+{
+ struct packet_mreq mreq = {iface->ifindex, PACKET_MR_ALLMULTI, ETH_ALEN, {0}};
+ setsockopt(ndp_event.uloop.fd, SOL_PACKET, PACKET_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
+
+ struct ndp_neighbor *c, *n;
+ list_for_each_entry_safe(c, n, &neighbors, head)
+ if (c->iface == iface && (c->timeout == 0 || iface->ndp != RELAYD_RELAY || !enable))
+ modify_neighbor(&c->addr, c->iface, false);
+
+ if (enable && iface->ndp == RELAYD_RELAY) {
+ setsockopt(ndp_event.uloop.fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+
+ if (iface->static_ndp_len) {
+ char *entry = alloca(iface->static_ndp_len), *saveptr;
+ memcpy(entry, iface->static_ndp, iface->static_ndp_len);
+
+ for (entry = strtok_r(entry, " ", &saveptr); entry; entry = strtok_r(NULL, " ", &saveptr)) {
+ struct ndp_neighbor *n = malloc(sizeof(*n));
+ n->iface = iface;
+ n->timeout = 0;
+
+ char ipbuf[INET6_ADDRSTRLEN];
+ if (sscanf(entry, "%45s/%hhu", ipbuf, &n->len) < 2
+ || n->len > 128 || inet_pton(AF_INET6, ipbuf, &n->addr) != 1) {
+ syslog(LOG_ERR, "Invalid static NDP-prefix %s", entry);
+ return -1;
+ }
+
+ list_add(&n->head, &neighbors);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+// Send an ICMP-ECHO. This is less for actually pinging but for the
+// neighbor cache to be kept up-to-date.
+static ssize_t ping6(struct in6_addr *addr,
+ const struct interface *iface)
+{
+ struct sockaddr_in6 dest = {AF_INET6, 0, 0, *addr, 0};
+ struct icmp6_hdr echo = {.icmp6_type = ICMP6_ECHO_REQUEST};
+ struct iovec iov = {&echo, sizeof(echo)};
+
+ // Linux seems to not honor IPV6_PKTINFO on raw-sockets, so work around
+ setsockopt(ping_socket, SOL_SOCKET, SO_BINDTODEVICE,
+ iface->ifname, sizeof(iface->ifname));
+ return odhcpd_send(ping_socket, &dest, &iov, 1, iface);
+}
+
+
+// Handle solicitations
+static void handle_solicit(void *addr, void *data, size_t len,
+ struct interface *iface)
+{
+ struct ip6_hdr *ip6 = data;
+ struct nd_neighbor_solicit *req = (struct nd_neighbor_solicit*)&ip6[1];
+ struct sockaddr_ll *ll = addr;
+
+ // Solicitation is for duplicate address detection
+ bool ns_is_dad = IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src);
+
+ // Don't forward any non-DAD solicitation for external ifaces
+ // TODO: check if we should even forward DADs for them
+ if (iface->external && !ns_is_dad)
+ return;
+
+ if (len < sizeof(*ip6) + sizeof(*req))
+ return; // Invalid reqicitation
+
+ if (IN6_IS_ADDR_LINKLOCAL(&req->nd_ns_target) ||
+ IN6_IS_ADDR_LOOPBACK(&req->nd_ns_target) ||
+ IN6_IS_ADDR_MULTICAST(&req->nd_ns_target))
+ return; // Invalid target
+
+ char ipbuf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &req->nd_ns_target, ipbuf, sizeof(ipbuf));
+ syslog(LOG_NOTICE, "Got a NS for %s", ipbuf);
+
+ uint8_t mac[6];
+ odhcpd_get_mac(iface, mac);
+ if (!memcmp(ll->sll_addr, mac, sizeof(mac)) &&
+ ll->sll_pkttype != PACKET_OUTGOING)
+ return; // Looped back
+
+ time_t now = time(NULL);
+
+ struct ndp_neighbor *n = find_neighbor(&req->nd_ns_target, false);
+ if (n && (n->iface || abs(n->timeout - now) < 5)) {
+ syslog(LOG_NOTICE, "%s is on %s", ipbuf,
+ (n->iface) ? n->iface->ifname : "<pending>");
+ if (!n->iface || n->iface == iface)
+ return;
+
+ // Found on other interface, answer with advertisement
+ struct {
+ struct nd_neighbor_advert body;
+ struct nd_opt_hdr opt_ll_hdr;
+ uint8_t mac[6];
+ } advert = {
+ .body = {
+ .nd_na_hdr = {ND_NEIGHBOR_ADVERT,
+ 0, 0, {{0}}},
+ .nd_na_target = req->nd_ns_target,
+ },
+ .opt_ll_hdr = {ND_OPT_TARGET_LINKADDR, 1},
+ };
+
+ memcpy(advert.mac, mac, sizeof(advert.mac));
+ advert.body.nd_na_flags_reserved = ND_NA_FLAG_ROUTER |
+ ND_NA_FLAG_SOLICITED;
+
+ struct sockaddr_in6 dest = {AF_INET6, 0, 0, ALL_IPV6_NODES, 0};
+ if (!ns_is_dad) // If not DAD, then unicast to source
+ dest.sin6_addr = ip6->ip6_src;
+
+ // Linux seems to not honor IPV6_PKTINFO on raw-sockets, so work around
+ setsockopt(ping_socket, SOL_SOCKET, SO_BINDTODEVICE,
+ iface->ifname, sizeof(iface->ifname));
+ struct iovec iov = {&advert, sizeof(advert)};
+ odhcpd_send(ping_socket, &dest, &iov, 1, iface);
+ } else {
+ // Send echo to all other interfaces to see where target is on
+ // This will trigger neighbor discovery which is what we want.
+ // We will observe the neighbor cache to see results.
+
+ ssize_t sent = 0;
+ struct interface *c;
+ list_for_each_entry(c, &interfaces, head)
+ if (iface->ndp == RELAYD_RELAY && iface != c &&
+ (!ns_is_dad || !c->external == false))
+ sent += ping6(&req->nd_ns_target, c);
+
+ if (sent > 0) // Sent a ping, add pending neighbor entry
+ modify_neighbor(&req->nd_ns_target, NULL, true);
+ }
+}
+
+
+void odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
+ const struct interface *iface, const struct in6_addr *gw, bool add)
+{
+ struct req {
+ struct nlmsghdr nh;
+ struct rtmsg rtm;
+ struct rtattr rta_dst;
+ struct in6_addr dst_addr;
+ struct rtattr rta_oif;
+ uint32_t ifindex;
+ struct rtattr rta_table;
+ uint32_t table;
+ struct rtattr rta_gw;
+ struct in6_addr gw;
+ } req = {
+ {sizeof(req), 0, NLM_F_REQUEST, ++rtnl_seqid, 0},
+ {AF_INET6, prefixlen, 0, 0, 0, 0, 0, 0, 0},
+ {sizeof(struct rtattr) + sizeof(struct in6_addr), RTA_DST},
+ *addr,
+ {sizeof(struct rtattr) + sizeof(uint32_t), RTA_OIF},
+ iface->ifindex,
+ {sizeof(struct rtattr) + sizeof(uint32_t), RTA_TABLE},
+ RT_TABLE_MAIN,
+ {sizeof(struct rtattr) + sizeof(struct in6_addr), RTA_GATEWAY},
+ IN6ADDR_ANY_INIT,
+ };
+
+ if (gw)
+ req.gw = *gw;
+
+ if (add) {
+ req.nh.nlmsg_type = RTM_NEWROUTE;
+ req.nh.nlmsg_flags |= (NLM_F_CREATE | NLM_F_REPLACE);
+ req.rtm.rtm_protocol = RTPROT_BOOT;
+ req.rtm.rtm_scope = (gw) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
+ req.rtm.rtm_type = RTN_UNICAST;
+ } else {
+ req.nh.nlmsg_type = RTM_DELROUTE;
+ req.rtm.rtm_scope = RT_SCOPE_NOWHERE;
+ }
+
+ size_t reqlen = (gw) ? sizeof(req) : offsetof(struct req, rta_gw);
+ send(rtnl_event.uloop.fd, &req, reqlen, MSG_DONTWAIT);
+}
+
+// Use rtnetlink to modify kernel routes
+static void setup_route(struct in6_addr *addr, struct interface *iface,
+ bool add)
+{
+ char namebuf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, addr, namebuf, sizeof(namebuf));
+ syslog(LOG_NOTICE, "%s about %s on %s", (add) ? "Learned" : "Forgot",
+ namebuf, (iface) ? iface->ifname : "<pending>");
+
+ if (!iface || !iface->learn_routes)
+ return;
+
+ odhcpd_setup_route(addr, 128, iface, NULL, add);
+}
+
+static void free_neighbor(struct ndp_neighbor *n)
+{
+ setup_route(&n->addr, n->iface, false);
+ list_del(&n->head);
+ free(n);
+ --neighbor_count;
+}
+
+
+static bool match_neighbor(struct ndp_neighbor *n, struct in6_addr *addr)
+{
+ if (n->len <= 32)
+ return ntohl(n->addr.s6_addr32[0]) >> (32 - n->len) ==
+ ntohl(addr->s6_addr32[0]) >> (32 - n->len);
+
+ if (n->addr.s6_addr32[0] != addr->s6_addr32[0])
+ return false;
+
+ if (n->len <= 64)
+ return ntohl(n->addr.s6_addr32[1]) >> (64 - n->len) ==
+ ntohl(addr->s6_addr32[1]) >> (64 - n->len);
+
+ if (n->addr.s6_addr32[1] != addr->s6_addr32[1])
+ return false;
+
+ if (n->len <= 96)
+ return ntohl(n->addr.s6_addr32[2]) >> (96 - n->len) ==
+ ntohl(addr->s6_addr32[2]) >> (96 - n->len);
+
+ if (n->addr.s6_addr32[2] != addr->s6_addr32[2])
+ return false;
+
+ return ntohl(n->addr.s6_addr32[3]) >> (128 - n->len) ==
+ ntohl(addr->s6_addr32[3]) >> (128 - n->len);
+}
+
+
+static struct ndp_neighbor* find_neighbor(struct in6_addr *addr, bool strict)
+{
+ time_t now = time(NULL);
+ struct ndp_neighbor *n, *e;
+ list_for_each_entry_safe(n, e, &neighbors, head) {
+ if ((!strict && match_neighbor(n, addr)) ||
+ (n->len == 128 && IN6_ARE_ADDR_EQUAL(&n->addr, addr)))
+ return n;
+
+ if (!n->iface && abs(n->timeout - now) >= 5)
+ free_neighbor(n);
+ }
+ return NULL;
+}
+
+
+// Modified our own neighbor-entries
+static void modify_neighbor(struct in6_addr *addr,
+ struct interface *iface, bool add)
+{
+ if (!addr || (void*)addr == (void*)iface)
+ return;
+
+ struct ndp_neighbor *n = find_neighbor(addr, true);
+ if (!add) { // Delete action
+ if (n && (!n->iface || n->iface == iface))
+ free_neighbor(n);
+ } else if (!n) { // No entry yet, add one if possible
+ if (neighbor_count >= NDP_MAX_NEIGHBORS ||
+ !(n = malloc(sizeof(*n))))
+ return;
+
+ n->len = 128;
+ n->addr = *addr;
+ n->iface = iface;
+ if (!n->iface)
+ time(&n->timeout);
+ list_add(&n->head, &neighbors);
+ ++neighbor_count;
+ setup_route(addr, n->iface, add);
+ } else if (n->iface == iface) {
+ if (!n->iface)
+ time(&n->timeout);
+ } else if (iface && (!n->iface ||
+ (!iface->external && n->iface->external))) {
+ setup_route(addr, n->iface, false);
+ n->iface = iface;
+ setup_route(addr, n->iface, add);
+ }
+ // TODO: In case a host switches interfaces we might want
+ // to set its old neighbor entry to NUD_STALE and ping it
+ // on the old interface to confirm if the MACs match.
+}
+
+
+// Handler for neighbor cache entries from the kernel. This is our source
+// to learn and unlearn hosts on interfaces.
+static void handle_rtnetlink(_unused void *addr, void *data, size_t len,
+ _unused struct interface *iface)
+{
+ for (struct nlmsghdr *nh = data; NLMSG_OK(nh, len);
+ nh = NLMSG_NEXT(nh, len)) {
+ struct rtmsg *rtm = NLMSG_DATA(nh);
+ if ((nh->nlmsg_type == RTM_NEWROUTE ||
+ nh->nlmsg_type == RTM_DELROUTE) &&
+ rtm->rtm_dst_len == 0)
+ raise(SIGUSR1); // Inform about a change in default route
+
+ struct ndmsg *ndm = NLMSG_DATA(nh);
+ struct ifaddrmsg *ifa = NLMSG_DATA(nh);
+ if (nh->nlmsg_type != RTM_NEWNEIGH
+ && nh->nlmsg_type != RTM_DELNEIGH
+ && nh->nlmsg_type != RTM_NEWADDR
+ && nh->nlmsg_type != RTM_DELADDR)
+ continue; // Unrelated message type
+ bool is_addr = (nh->nlmsg_type == RTM_NEWADDR
+ || nh->nlmsg_type == RTM_DELADDR);
+
+ // Family and ifindex are on the same offset for NEIGH and ADDR
+ if (NLMSG_PAYLOAD(nh, 0) < sizeof(*ndm)
+ || ndm->ndm_family != AF_INET6)
+ continue; //
+
+ // Lookup interface
+ struct interface *iface;
+ if (!(iface = odhcpd_get_interface_by_index(ndm->ndm_ifindex)))
+ continue;
+
+ // Data to retrieve
+ size_t rta_offset = (is_addr) ? sizeof(*ifa) : sizeof(*ndm);
+ uint16_t atype = (is_addr) ? IFA_ADDRESS : NDA_DST;
+ ssize_t alen = NLMSG_PAYLOAD(nh, rta_offset);
+ struct in6_addr *addr = NULL;
+
+ for (struct rtattr *rta = (void*)(((uint8_t*)ndm) + rta_offset);
+ RTA_OK(rta, alen); rta = RTA_NEXT(rta, alen))
+ if (rta->rta_type == atype &&
+ RTA_PAYLOAD(rta) >= sizeof(*addr))
+ addr = RTA_DATA(rta);
+
+ // Address not specified or unrelated
+ if (!addr || IN6_IS_ADDR_LINKLOCAL(addr) ||
+ IN6_IS_ADDR_MULTICAST(addr))
+ continue;
+
+ // Check for states
+ bool add;
+ if (is_addr)
+ add = (nh->nlmsg_type == RTM_NEWADDR);
+ else
+ add = (nh->nlmsg_type == RTM_NEWNEIGH && (ndm->ndm_state &
+ (NUD_REACHABLE | NUD_STALE | NUD_DELAY | NUD_PROBE
+ | NUD_PERMANENT | NUD_NOARP)));
+
+ if (iface->ndp == RELAYD_RELAY)
+ modify_neighbor(addr, iface, add);
+
+ if (is_addr && iface->ra == RELAYD_SERVER)
+ raise(SIGUSR1); // Inform about a change in addresses
+
+ if (is_addr && iface->dhcpv6 == RELAYD_SERVER)
+ iface->ia_reconf = true;
+
+ if (iface->ndp == RELAYD_RELAY && is_addr && iface->master) {
+ // Replay address changes on all slave interfaces
+ nh->nlmsg_flags = NLM_F_REQUEST;
+
+ if (nh->nlmsg_type == RTM_NEWADDR)
+ nh->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE;
+
+ struct interface *c;
+ list_for_each_entry(c, &interfaces, head) {
+ if (c->ndp == RELAYD_RELAY && !c->master) {
+ ifa->ifa_index = c->ifindex;
+ send(rtnl_event.uloop.fd, nh, nh->nlmsg_len, MSG_DONTWAIT);
+ }
+ }
+ }
+
+ /* TODO: See if this is required for optimal operation
+ // Keep neighbor entries alive so we don't loose routes
+ if (add && (ndm->ndm_state & NUD_STALE))
+ ping6(addr, iface);
+ */
+ }
+}
diff --git a/src/ndp.h b/src/ndp.h
new file mode 100644
index 0000000..abd5b0a
--- /dev/null
+++ b/src/ndp.h
@@ -0,0 +1,31 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#pragma once
+#include "odhcpd.h"
+#include <time.h>
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#define NDP_MAX_NEIGHBORS 1000
+
+struct ndp_neighbor {
+ struct list_head head;
+ struct interface *iface;
+ struct in6_addr addr;
+ uint8_t len;
+ time_t timeout;
+};
diff --git a/src/odhcpd.c b/src/odhcpd.c
new file mode 100644
index 0000000..6070bfb
--- /dev/null
+++ b/src/odhcpd.c
@@ -0,0 +1,423 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <resolv.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/ip6.h>
+#include <netpacket/packet.h>
+#include <linux/rtnetlink.h>
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+
+#include <libubox/uloop.h>
+#include "odhcpd.h"
+
+
+
+static int ioctl_sock;
+static int rtnl_socket = -1;
+static int rtnl_seq = 0;
+static int urandom_fd = -1;
+
+
+int main()
+{
+ openlog("odhcpd", LOG_PERROR | LOG_PID, LOG_DAEMON);
+ uloop_init();
+
+ if (getuid() != 0) {
+ syslog(LOG_ERR, "Must be run as root!");
+ return 2;
+ }
+
+ ioctl_sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+
+ if ((rtnl_socket = odhcpd_open_rtnl()) < 0) {
+ syslog(LOG_ERR, "Unable to open socket: %s", strerror(errno));
+ return 2;
+ }
+
+ if ((urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC)) < 0)
+ return 4;
+
+ signal(SIGUSR1, SIG_IGN);
+
+ if (init_router())
+ return 4;
+
+ if (init_dhcpv6())
+ return 4;
+
+ if (init_ndp())
+ return 4;
+
+ if (init_dhcpv4())
+ return 4;
+
+ if (init_ubus())
+ return 4;
+
+ odhcpd_run();
+ return 0;
+}
+
+int odhcpd_open_rtnl(void)
+{
+ int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+
+ // Connect to the kernel netlink interface
+ struct sockaddr_nl nl = {.nl_family = AF_NETLINK};
+ if (connect(sock, (struct sockaddr*)&nl, sizeof(nl))) {
+ syslog(LOG_ERR, "Failed to connect to kernel rtnetlink: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ return sock;
+}
+
+
+// Read IPv6 MTU for interface
+int odhcpd_get_interface_mtu(const char *ifname)
+{
+ char buf[64];
+ const char *sysctl_pattern = "/proc/sys/net/ipv6/conf/%s/mtu";
+ snprintf(buf, sizeof(buf), sysctl_pattern, ifname);
+
+ int fd = open(buf, O_RDONLY);
+ ssize_t len = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+
+ if (len < 0)
+ return -1;
+
+
+ buf[len] = 0;
+ return atoi(buf);
+
+}
+
+
+// Read IPv6 MAC for interface
+int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6])
+{
+ struct ifreq ifr;
+ strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
+ if (ioctl(ioctl_sock, SIOCGIFHWADDR, &ifr) < 0)
+ return -1;
+ memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
+ return 0;
+}
+
+
+// Forwards a packet on a specific interface
+ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
+ struct iovec *iov, size_t iov_len,
+ const struct interface *iface)
+{
+ // Construct headers
+ uint8_t cmsg_buf[CMSG_SPACE(sizeof(struct in6_pktinfo))] = {0};
+ struct msghdr msg = {(void*)dest, sizeof(*dest), iov, iov_len,
+ cmsg_buf, sizeof(cmsg_buf), 0};
+
+ // Set control data (define destination interface)
+ struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg);
+ chdr->cmsg_level = IPPROTO_IPV6;
+ chdr->cmsg_type = IPV6_PKTINFO;
+ chdr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ struct in6_pktinfo *pktinfo = (struct in6_pktinfo*)CMSG_DATA(chdr);
+ pktinfo->ipi6_ifindex = iface->ifindex;
+
+ // Also set scope ID if link-local
+ if (IN6_IS_ADDR_LINKLOCAL(&dest->sin6_addr)
+ || IN6_IS_ADDR_MC_LINKLOCAL(&dest->sin6_addr))
+ dest->sin6_scope_id = iface->ifindex;
+
+ // IPV6_PKTINFO doesn't really work for IPv6-raw sockets (bug?)
+ if (dest->sin6_port == 0) {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ }
+
+ char ipbuf[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &dest->sin6_addr, ipbuf, sizeof(ipbuf));
+
+ ssize_t sent = sendmsg(socket, &msg, MSG_DONTWAIT);
+ if (sent < 0)
+ syslog(LOG_WARNING, "Failed to send to %s%%%s (%s)",
+ ipbuf, iface->ifname, strerror(errno));
+ else
+ syslog(LOG_NOTICE, "Sent %li bytes to %s%%%s",
+ (long)sent, ipbuf, iface->ifname);
+ return sent;
+}
+
+
+// Detect an IPV6-address currently assigned to the given interface
+ssize_t odhcpd_get_interface_addresses(int ifindex,
+ struct odhcpd_ipaddr *addrs, size_t cnt)
+{
+ struct {
+ struct nlmsghdr nhm;
+ struct ifaddrmsg ifa;
+ } req = {{sizeof(req), RTM_GETADDR, NLM_F_REQUEST | NLM_F_DUMP,
+ ++rtnl_seq, 0}, {AF_INET6, 0, 0, 0, ifindex}};
+ if (send(rtnl_socket, &req, sizeof(req), 0) < (ssize_t)sizeof(req))
+ return 0;
+
+ uint8_t buf[8192];
+ ssize_t len = 0, ret = 0;
+
+ for (struct nlmsghdr *nhm = NULL; ; nhm = NLMSG_NEXT(nhm, len)) {
+ while (len < 0 || !NLMSG_OK(nhm, (size_t)len)) {
+ len = recv(rtnl_socket, buf, sizeof(buf), 0);
+ nhm = (struct nlmsghdr*)buf;
+ if (len < 0 || !NLMSG_OK(nhm, (size_t)len)) {
+ if (errno == EINTR)
+ continue;
+ else
+ return ret;
+ }
+ }
+
+ if (nhm->nlmsg_type != RTM_NEWADDR)
+ break;
+
+ // Skip address but keep clearing socket buffer
+ if (ret >= (ssize_t)cnt)
+ continue;
+
+ struct ifaddrmsg *ifa = NLMSG_DATA(nhm);
+ if (ifa->ifa_scope != RT_SCOPE_UNIVERSE ||
+ ifa->ifa_index != (unsigned)ifindex)
+ continue;
+
+ struct rtattr *rta = (struct rtattr*)&ifa[1];
+ size_t alen = NLMSG_PAYLOAD(nhm, sizeof(*ifa));
+ memset(&addrs[ret], 0, sizeof(addrs[ret]));
+ addrs[ret].prefix = ifa->ifa_prefixlen;
+
+ while (RTA_OK(rta, alen)) {
+ if (rta->rta_type == IFA_ADDRESS) {
+ memcpy(&addrs[ret].addr, RTA_DATA(rta),
+ sizeof(struct in6_addr));
+ } else if (rta->rta_type == IFA_CACHEINFO) {
+ struct ifa_cacheinfo *ifc = RTA_DATA(rta);
+ addrs[ret].preferred = ifc->ifa_prefered;
+ addrs[ret].valid = ifc->ifa_valid;
+ }
+
+ rta = RTA_NEXT(rta, alen);
+ }
+
+ if (ifa->ifa_flags & IFA_F_DEPRECATED)
+ addrs[ret].preferred = 0;
+
+ ++ret;
+ }
+
+ return ret;
+}
+
+
+struct interface* odhcpd_get_interface_by_index(int ifindex)
+{
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head)
+ if (iface->ifindex == ifindex)
+ return iface;
+
+ return NULL;
+}
+
+
+struct interface* odhcpd_get_interface_by_name(const char *name)
+{
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head)
+ if (!strcmp(iface->ifname, name))
+ return iface;
+
+ return NULL;
+}
+
+
+struct interface* odhcpd_get_master_interface(void)
+{
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head)
+ if (iface->master)
+ return iface;
+
+ return NULL;
+}
+
+
+// Convenience function to receive and do basic validation of packets
+static void odhcpd_receive_packets(struct uloop_fd *u, _unused unsigned int events)
+{
+ struct odhcpd_event *e = container_of(u, struct odhcpd_event, uloop);
+
+ uint8_t data_buf[RELAYD_BUFFER_SIZE], cmsg_buf[128];
+ union {
+ struct sockaddr_in6 in6;
+ struct sockaddr_in in;
+ struct sockaddr_ll ll;
+ struct sockaddr_nl nl;
+ } addr;
+
+ while (true) {
+ struct iovec iov = {data_buf, sizeof(data_buf)};
+ struct msghdr msg = {&addr, sizeof(addr), &iov, 1,
+ cmsg_buf, sizeof(cmsg_buf), 0};
+
+ ssize_t len = recvmsg(u->fd, &msg, MSG_DONTWAIT);
+ if (len < 0) {
+ if (errno == EAGAIN)
+ break;
+ else
+ continue;
+ }
+
+
+ // Extract destination interface
+ int destiface = 0;
+ int *hlim = NULL;
+ struct in6_pktinfo *pktinfo;
+ struct in_pktinfo *pkt4info;
+ for (struct cmsghdr *ch = CMSG_FIRSTHDR(&msg); ch != NULL &&
+ destiface == 0; ch = CMSG_NXTHDR(&msg, ch)) {
+ if (ch->cmsg_level == IPPROTO_IPV6 &&
+ ch->cmsg_type == IPV6_PKTINFO) {
+ pktinfo = (struct in6_pktinfo*)CMSG_DATA(ch);
+ destiface = pktinfo->ipi6_ifindex;
+ } else if (ch->cmsg_level == IPPROTO_IP &&
+ ch->cmsg_type == IP_PKTINFO) {
+ pkt4info = (struct in_pktinfo*)CMSG_DATA(ch);
+ destiface = pkt4info->ipi_ifindex;
+ } else if (ch->cmsg_level == IPPROTO_IPV6 &&
+ ch->cmsg_type == IPV6_HOPLIMIT) {
+ hlim = (int*)CMSG_DATA(ch);
+ }
+ }
+
+ // Check hoplimit if received
+ if (hlim && *hlim != 255)
+ continue;
+
+ // Detect interface for packet sockets
+ if (addr.ll.sll_family == AF_PACKET)
+ destiface = addr.ll.sll_ifindex;
+
+ struct interface *iface =
+ odhcpd_get_interface_by_index(destiface);
+
+ if (!iface && addr.nl.nl_family != AF_NETLINK)
+ continue;
+
+ char ipbuf[INET6_ADDRSTRLEN] = "kernel";
+ if (addr.ll.sll_family == AF_PACKET &&
+ len >= (ssize_t)sizeof(struct ip6_hdr))
+ inet_ntop(AF_INET6, &data_buf[8], ipbuf, sizeof(ipbuf));
+ else if (addr.in6.sin6_family == AF_INET6)
+ inet_ntop(AF_INET6, &addr.in6.sin6_addr, ipbuf, sizeof(ipbuf));
+ else if (addr.in.sin_family == AF_INET)
+ inet_ntop(AF_INET, &addr.in.sin_addr, ipbuf, sizeof(ipbuf));
+
+ syslog(LOG_NOTICE, "--");
+ syslog(LOG_NOTICE, "Received %li Bytes from %s%%%s", (long)len,
+ ipbuf, (iface) ? iface->ifname : "netlink");
+
+ e->handle_dgram(&addr, data_buf, len, iface);
+ }
+}
+
+// Register events for the multiplexer
+int odhcpd_register(struct odhcpd_event *event)
+{
+ event->uloop.cb = odhcpd_receive_packets;
+ return uloop_fd_add(&event->uloop, ULOOP_READ);
+}
+
+void odhcpd_urandom(void *data, size_t len)
+{
+ read(urandom_fd, data, len);
+}
+
+
+time_t odhcpd_time(void)
+{
+ struct timespec ts;
+ syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec;
+}
+
+
+static const char hexdigits[] = "0123456789abcdef";
+static const int8_t hexvals[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+ssize_t odhcpd_unhexlify(uint8_t *dst, size_t len, const char *src)
+{
+ size_t c;
+ for (c = 0; c < len && src[0] && src[1]; ++c) {
+ int8_t x = (int8_t)*src++;
+ int8_t y = (int8_t)*src++;
+ if (x < 0 || (x = hexvals[x]) < 0
+ || y < 0 || (y = hexvals[y]) < 0)
+ return -1;
+ dst[c] = x << 4 | y;
+ while (((int8_t)*src) < 0 ||
+ (*src && hexvals[(uint8_t)*src] < 0))
+ src++;
+ }
+
+ return c;
+}
+
+
+void odhcpd_hexlify(char *dst, const uint8_t *src, size_t len)
+{
+ for (size_t i = 0; i < len; ++i) {
+ *dst++ = hexdigits[src[i] >> 4];
+ *dst++ = hexdigits[src[i] & 0x0f];
+ }
+ *dst = 0;
+}
diff --git a/src/odhcpd.h b/src/odhcpd.h
new file mode 100644
index 0000000..5608fa9
--- /dev/null
+++ b/src/odhcpd.h
@@ -0,0 +1,205 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#pragma once
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <stdbool.h>
+#include <syslog.h>
+
+#include "libubox/blobmsg.h"
+
+#ifndef typeof
+#define typeof __typeof
+#endif
+
+#ifndef container_of
+#define container_of(ptr, type, member) ( \
+ (type *)( (char *)ptr - offsetof(type,member) ))
+#endif
+
+#include "libubox/list.h"
+#include "libubox/uloop.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+// RFC 6106 defines this router advertisement option
+#define ND_OPT_ROUTE_INFO 24
+#define ND_OPT_RECURSIVE_DNS 25
+#define ND_OPT_DNS_SEARCH 31
+
+#define RELAYD_BUFFER_SIZE 8192
+#define RELAYD_MAX_PREFIXES 8
+
+#define _unused __attribute__((unused))
+#define _packed __attribute__((packed))
+
+
+#define ALL_IPV6_NODES {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}
+
+#define ALL_IPV6_ROUTERS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}}
+
+
+struct interface;
+extern struct list_head leases;
+
+struct odhcpd_event {
+ struct uloop_fd uloop;
+ void (*handle_dgram)(void *addr, void *data, size_t len,
+ struct interface *iface);
+};
+
+
+struct odhcpd_ipaddr {
+ struct in6_addr addr;
+ uint8_t prefix;
+ uint32_t preferred;
+ uint32_t valid;
+};
+
+enum odhcpd_mode {
+ RELAYD_DISABLED,
+ RELAYD_SERVER,
+ RELAYD_RELAY,
+ RELAYD_HYBRID
+};
+
+
+struct config {
+ bool legacy;
+ char *dhcp_cb;
+ char *dhcp_statefile;
+} config;
+
+
+struct lease {
+ struct list_head head;
+ struct in_addr ipaddr;
+ uint32_t hostid;
+ struct ether_addr mac;
+ uint16_t duid_len;
+ uint8_t *duid;
+ char hostname[];
+};
+
+
+struct interface {
+ struct list_head head;
+
+ int ifindex;
+ char ifname[IF_NAMESIZE];
+ char name[IF_NAMESIZE];
+ bool inuse;
+
+ // Runtime data
+ struct uloop_timeout timer_rs;
+ struct list_head ia_assignments;
+ struct odhcpd_ipaddr ia_addr[8];
+ size_t ia_addr_len;
+ bool ia_reconf;
+
+ // DHCPv4
+ struct odhcpd_event dhcpv4_event;
+ struct list_head dhcpv4_assignments;
+
+ // Services
+ enum odhcpd_mode ra;
+ enum odhcpd_mode dhcpv6;
+ enum odhcpd_mode ndp;
+ enum odhcpd_mode dhcpv4;
+
+ // Config
+ bool external;
+ bool master;
+ bool ignore;
+ bool always_rewrite_dns;
+ bool deprecate_ula_if_public_avail;
+ bool ra_not_onlink;
+ bool no_dynamic_dhcp;
+
+ int learn_routes;
+ int default_router;
+ int managed;
+ int route_preference;
+
+ // DHCPv4
+ struct in_addr dhcpv4_start;
+ struct in_addr dhcpv4_end;
+ struct in_addr *dhcpv4_dns;
+ size_t dhcpv4_dns_cnt;
+ uint32_t dhcpv4_leasetime;
+
+ // DNS
+ struct in6_addr *dns;
+ size_t dns_cnt;
+ uint8_t *search;
+ size_t search_len;
+
+ char* static_ndp;
+ size_t static_ndp_len;
+
+ char *upstream;
+ size_t upstream_len;
+};
+
+extern struct list_head interfaces;
+
+#define RELAYD_MANAGED_MFLAG 1
+#define RELAYD_MANAGED_NO_AFLAG 2
+
+
+// Exported main functions
+int odhcpd_open_rtnl(void);
+int odhcpd_register(struct odhcpd_event *event);
+
+ssize_t odhcpd_send(int socket, struct sockaddr_in6 *dest,
+ struct iovec *iov, size_t iov_len,
+ const struct interface *iface);
+ssize_t odhcpd_get_interface_addresses(int ifindex,
+ struct odhcpd_ipaddr *addrs, size_t cnt);
+struct interface* odhcpd_get_interface_by_name(const char *name);
+int odhcpd_get_interface_mtu(const char *ifname);
+int odhcpd_get_mac(const struct interface *iface, uint8_t mac[6]);
+struct interface* odhcpd_get_interface_by_index(int ifindex);
+struct interface* odhcpd_get_master_interface(void);
+void odhcpd_urandom(void *data, size_t len);
+void odhcpd_setup_route(const struct in6_addr *addr, int prefixlen,
+ const struct interface *iface, const struct in6_addr *gw, bool add);
+
+void odhcpd_run(void);
+time_t odhcpd_time(void);
+ssize_t odhcpd_unhexlify(uint8_t *dst, size_t len, const char *src);
+void odhcpd_hexlify(char *dst, const uint8_t *src, size_t len);
+
+int config_parse_interface(struct blob_attr *b, const char *iname);
+
+const char* ubus_get_ifname(const char *name);
+void ubus_apply_network(void);
+
+
+// Exported module initializers
+int init_router(void);
+int init_dhcpv6(void);
+int init_dhcpv4(void);
+int init_ndp(void);
+int init_ubus(void);
+
+int setup_router_interface(struct interface *iface, bool enable);
+int setup_dhcpv6_interface(struct interface *iface, bool enable);
+int setup_ndp_interface(struct interface *iface, bool enable);
+int setup_dhcpv4_interface(struct interface *iface, bool enable);
diff --git a/src/router.c b/src/router.c
new file mode 100644
index 0000000..9258acf
--- /dev/null
+++ b/src/router.c
@@ -0,0 +1,502 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <net/route.h>
+
+#include "router.h"
+#include "odhcpd.h"
+
+
+static void forward_router_solicitation(const struct interface *iface);
+static void forward_router_advertisement(uint8_t *data, size_t len);
+
+static void handle_icmpv6(void *addr, void *data, size_t len,
+ struct interface *iface);
+static void send_router_advert(struct uloop_timeout *event);
+static void sigusr1_refresh(int signal);
+
+static struct odhcpd_event router_event = {{.fd = -1}, handle_icmpv6};
+
+static FILE *fp_route = NULL;
+
+
+int init_router(void)
+{
+ // Open ICMPv6 socket
+ int sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6);
+ if (sock < 0 && errno != EAFNOSUPPORT) {
+ syslog(LOG_ERR, "Failed to open RAW-socket: %s", strerror(errno));
+ return -1;
+ }
+
+ // Let the kernel compute our checksums
+ int val = 2;
+ setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val));
+
+ // This is required by RFC 4861
+ val = 255;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val));
+ setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val));
+
+ // We need to know the source interface
+ val = 1;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, sizeof(val));
+ setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, sizeof(val));
+
+ // Don't loop back
+ val = 0;
+ setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof(val));
+
+ // Filter ICMPv6 package types
+ struct icmp6_filter filt;
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filt);
+ setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt));
+
+ // Register socket
+ router_event.uloop.fd = sock;
+ odhcpd_register(&router_event);
+
+ if (!(fp_route = fopen("/proc/net/ipv6_route", "r")))
+ syslog(LOG_ERR, "Failed to open routing table: %s",
+ strerror(errno));
+
+ signal(SIGUSR1, sigusr1_refresh);
+ return 0;
+}
+
+
+int setup_router_interface(struct interface *iface, bool enable)
+{
+ struct ipv6_mreq all_nodes = {ALL_IPV6_NODES, iface->ifindex};
+ struct ipv6_mreq all_routers = {ALL_IPV6_ROUTERS, iface->ifindex};
+
+ uloop_timeout_cancel(&iface->timer_rs);
+ iface->timer_rs.cb = NULL;
+
+ setsockopt(router_event.uloop.fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP,
+ &all_nodes, sizeof(all_nodes));
+ setsockopt(router_event.uloop.fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP,
+ &all_routers, sizeof(all_routers));
+
+ if (!enable) {
+ if (iface->ra)
+ send_router_advert(&iface->timer_rs);
+ } else {
+ void *mreq = &all_routers;
+
+ if (iface->ra == RELAYD_RELAY && iface->master) {
+ mreq = &all_nodes;
+ forward_router_solicitation(iface);
+ } else if (iface->ra == RELAYD_SERVER && !iface->master) {
+ iface->timer_rs.cb = send_router_advert;
+ send_router_advert(&iface->timer_rs);
+ }
+
+ if (iface->ra == RELAYD_RELAY || (iface->ra == RELAYD_SERVER && !iface->master))
+ setsockopt(router_event.uloop.fd, IPPROTO_IPV6,
+ IPV6_ADD_MEMBERSHIP, mreq, sizeof(all_nodes));
+ }
+ return 0;
+}
+
+
+// Signal handler to resend all RDs
+static void sigusr1_refresh(_unused int signal)
+{
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head)
+ if (iface->ra == RELAYD_SERVER && !iface->master)
+ uloop_timeout_set(&iface->timer_rs, 1000);
+}
+
+
+// Event handler for incoming ICMPv6 packets
+static void handle_icmpv6(_unused void *addr, void *data, size_t len,
+ struct interface *iface)
+{
+ struct icmp6_hdr *hdr = data;
+ if ((iface->ra == RELAYD_SERVER && !iface->master)) { // Server mode
+ if (hdr->icmp6_type == ND_ROUTER_SOLICIT)
+ send_router_advert(&iface->timer_rs);
+ } else if (iface->ra == RELAYD_RELAY) { // Relay mode
+ if (hdr->icmp6_type == ND_ROUTER_ADVERT && iface->master)
+ forward_router_advertisement(data, len);
+ else if (hdr->icmp6_type == ND_ROUTER_SOLICIT && !iface->master)
+ forward_router_solicitation(odhcpd_get_master_interface());
+ }
+}
+
+
+static bool match_route(const struct odhcpd_ipaddr *n, const struct in6_addr *addr)
+{
+ if (n->prefix <= 32)
+ return ntohl(n->addr.s6_addr32[0]) >> (32 - n->prefix) ==
+ ntohl(addr->s6_addr32[0]) >> (32 - n->prefix);
+
+ if (n->addr.s6_addr32[0] != addr->s6_addr32[0])
+ return false;
+
+ return ntohl(n->addr.s6_addr32[1]) >> (64 - n->prefix) ==
+ ntohl(addr->s6_addr32[1]) >> (64 - n->prefix);
+}
+
+
+// Detect whether a default route exists, also find the source prefixes
+static bool parse_routes(struct odhcpd_ipaddr *n, ssize_t len)
+{
+ rewind(fp_route);
+
+ char line[512], ifname[16];
+ bool found_default = false;
+ struct odhcpd_ipaddr p = {IN6ADDR_ANY_INIT, 0, 0, 0};
+ while (fgets(line, sizeof(line), fp_route)) {
+ uint32_t rflags;
+ if (sscanf(line, "00000000000000000000000000000000 00 "
+ "%*s %*s %*s %*s %*s %*s %*s %15s", ifname) &&
+ strcmp(ifname, "lo")) {
+ found_default = true;
+ } else if (sscanf(line, "%8" SCNx32 "%8" SCNx32 "%*8" SCNx32 "%*8" SCNx32 " %hhx %*s "
+ "%*s 00000000000000000000000000000000 %*s %*s %*s %" SCNx32 " lo",
+ &p.addr.s6_addr32[0], &p.addr.s6_addr32[1], &p.prefix, &rflags) &&
+ p.prefix > 0 && (rflags & RTF_NONEXTHOP) && (rflags & RTF_REJECT)) {
+ // Find source prefixes by scanning through unreachable-routes
+ p.addr.s6_addr32[0] = htonl(p.addr.s6_addr32[0]);
+ p.addr.s6_addr32[1] = htonl(p.addr.s6_addr32[1]);
+
+ for (ssize_t i = 0; i < len; ++i) {
+ if (n[i].prefix <= 64 && n[i].prefix >= p.prefix &&
+ match_route(&p, &n[i].addr)) {
+ n[i].prefix = p.prefix;
+ break;
+ }
+ }
+
+ }
+ }
+
+ return found_default;
+}
+
+
+// Router Advert server mode
+static void send_router_advert(struct uloop_timeout *event)
+{
+ struct interface *iface =
+ container_of(event, struct interface, timer_rs);
+
+ int mtu = odhcpd_get_interface_mtu(iface->ifname);
+ if (mtu < 0)
+ mtu = 1500;
+
+ struct {
+ struct nd_router_advert h;
+ struct icmpv6_opt lladdr;
+ struct nd_opt_mtu mtu;
+ struct nd_opt_prefix_info prefix[RELAYD_MAX_PREFIXES];
+ } adv = {
+ .h = {{.icmp6_type = ND_ROUTER_ADVERT, .icmp6_code = 0}, 0, 0},
+ .lladdr = {ND_OPT_SOURCE_LINKADDR, 1, {0}},
+ .mtu = {ND_OPT_MTU, 1, 0, htonl(mtu)},
+ };
+ adv.h.nd_ra_flags_reserved = ND_RA_FLAG_OTHER;
+ if (iface->managed >= RELAYD_MANAGED_MFLAG)
+ adv.h.nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
+
+ if (iface->route_preference < 0)
+ adv.h.nd_ra_flags_reserved |= ND_RA_PREF_LOW;
+ else if (iface->route_preference > 0)
+ adv.h.nd_ra_flags_reserved |= ND_RA_PREF_HIGH;
+ odhcpd_get_mac(iface, adv.lladdr.data);
+
+ // If not currently shutting down
+ struct odhcpd_ipaddr addrs[RELAYD_MAX_PREFIXES];
+ ssize_t ipcnt = 0;
+
+ // If not shutdown
+ if (event->cb) {
+ ipcnt = odhcpd_get_interface_addresses(iface->ifindex,
+ addrs, ARRAY_SIZE(addrs));
+
+ // Check default route
+ if (parse_routes(addrs, ipcnt) || iface->default_router > 1)
+ adv.h.nd_ra_router_lifetime =
+ htons(3 * MaxRtrAdvInterval);
+ }
+
+ // Construct Prefix Information options
+ bool have_public = false;
+ size_t cnt = 0;
+
+ struct in6_addr *dns_addr = NULL;
+ uint32_t dns_time = 0;
+ size_t dns_cnt = 1;
+
+ for (ssize_t i = 0; i < ipcnt; ++i) {
+ struct odhcpd_ipaddr *addr = &addrs[i];
+ if (addr->prefix > 64)
+ continue; // Address not suitable
+
+ if (addr->preferred > MaxPreferredTime)
+ addr->preferred = MaxPreferredTime;
+
+ if (addr->valid > MaxValidTime)
+ addr->valid = MaxValidTime;
+
+ struct nd_opt_prefix_info *p = NULL;
+ for (size_t i = 0; i < cnt; ++i) {
+ if (!memcmp(&adv.prefix[i].nd_opt_pi_prefix,
+ &addr->addr, 8))
+ p = &adv.prefix[i];
+ }
+
+ if (!p) {
+ if (cnt >= ARRAY_SIZE(adv.prefix))
+ break;
+
+ p = &adv.prefix[cnt++];
+ }
+
+ if ((addr->addr.s6_addr[0] & 0xfe) != 0xfc && addr->preferred > 0)
+ have_public = true;
+
+ memcpy(&p->nd_opt_pi_prefix, &addr->addr, 8);
+ p->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
+ p->nd_opt_pi_len = 4;
+ p->nd_opt_pi_prefix_len = 64;
+ p->nd_opt_pi_flags_reserved = 0;
+ if (!iface->ra_not_onlink)
+ p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK;
+ if (iface->managed < RELAYD_MANAGED_NO_AFLAG)
+ p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
+ p->nd_opt_pi_valid_time = htonl(addr->valid);
+ p->nd_opt_pi_preferred_time = htonl(addr->preferred);
+
+ if (addr->preferred > dns_time) {
+ dns_time = addr->preferred;
+ dns_addr = &addr->addr;
+ }
+ }
+
+ if (!have_public && !iface->default_router && adv.h.nd_ra_router_lifetime) {
+ syslog(LOG_WARNING, "A default route is present but there is no public prefix "
+ "on %s thus we don't announce a default route!", iface->ifname);
+ adv.h.nd_ra_router_lifetime = 0;
+ }
+
+ if (have_public && iface->deprecate_ula_if_public_avail)
+ for (size_t i = 0; i < cnt; ++i)
+ if ((adv.prefix[i].nd_opt_pi_prefix.s6_addr[0] & 0xfe) == 0xfc)
+ adv.prefix[i].nd_opt_pi_preferred_time = 0;
+
+ // DNS Recursive DNS
+ if (iface->dns_cnt > 0) {
+ dns_addr = iface->dns;
+ dns_cnt = iface->dns_cnt;
+ dns_time = 2 * MaxRtrAdvInterval;
+ }
+
+ if (!dns_addr)
+ dns_cnt = 0;
+
+ struct {
+ uint8_t type;
+ uint8_t len;
+ uint8_t pad;
+ uint8_t pad2;
+ uint32_t lifetime;
+ } dns = {ND_OPT_RECURSIVE_DNS, (1 + (2 * dns_cnt)), 0, 0, htonl(dns_time)};
+
+
+
+ // DNS Search options
+ uint8_t search_buf[256], *search_domain = iface->search;
+ size_t search_len = iface->search_len, search_padded = 0;
+
+ if (!search_domain && !res_init() && _res.dnsrch[0] && _res.dnsrch[0][0]) {
+ int len = dn_comp(_res.dnsrch[0], search_buf,
+ sizeof(search_buf), NULL, NULL);
+ if (len > 0) {
+ search_domain = search_buf;
+ search_len = len;
+ }
+ }
+
+ if (search_len > 0)
+ search_padded = ((search_len + 7) & (~7)) + 8;
+
+ struct {
+ uint8_t type;
+ uint8_t len;
+ uint8_t pad;
+ uint8_t pad2;
+ uint32_t lifetime;
+ uint8_t name[];
+ } *search = alloca(sizeof(*search) + search_padded);
+ search->type = ND_OPT_DNS_SEARCH;
+ search->len = search_len ? ((sizeof(*search) + search_padded) / 8) : 0;
+ search->pad = 0;
+ search->pad2 = 0;
+ search->lifetime = htonl(2 * MaxRtrAdvInterval);;
+ memcpy(search->name, search_domain, search_len);
+ memset(&search->name[search_len], 0, search_padded - search_len);
+
+
+ size_t routes_cnt = 0;
+ struct {
+ uint8_t type;
+ uint8_t len;
+ uint8_t prefix;
+ uint8_t flags;
+ uint32_t lifetime;
+ uint32_t addr[4];
+ } routes[RELAYD_MAX_PREFIXES];
+
+ for (ssize_t i = 0; i < ipcnt; ++i) {
+ struct odhcpd_ipaddr *addr = &addrs[i];
+ if (addr->prefix > 64 || addr->prefix == 0) {
+ continue; // Address not suitable
+ } else if (addr->prefix > 32) {
+ addr->addr.s6_addr32[1] &= htonl(~((1U << (64 - addr->prefix)) - 1));
+ } else if (addr->prefix <= 32) {
+ addr->addr.s6_addr32[0] &= htonl(~((1U << (32 - addr->prefix)) - 1));
+ addr->addr.s6_addr32[1] = 0;
+ }
+
+ routes[routes_cnt].type = ND_OPT_ROUTE_INFO;
+ routes[routes_cnt].len = sizeof(*routes) / 8;
+ routes[routes_cnt].prefix = addr->prefix;
+ routes[routes_cnt].flags = 0;
+ if (iface->route_preference < 0)
+ routes[routes_cnt].flags |= ND_RA_PREF_LOW;
+ else if (iface->route_preference > 0)
+ routes[routes_cnt].flags |= ND_RA_PREF_HIGH;
+ routes[routes_cnt].lifetime = htonl(addr->valid);
+ routes[routes_cnt].addr[0] = addr->addr.s6_addr32[0];
+ routes[routes_cnt].addr[1] = addr->addr.s6_addr32[1];
+ routes[routes_cnt].addr[2] = addr->addr.s6_addr32[2];
+ routes[routes_cnt].addr[3] = addr->addr.s6_addr32[3];
+
+ ++routes_cnt;
+ }
+
+
+ struct iovec iov[] = {{&adv, (uint8_t*)&adv.prefix[cnt] - (uint8_t*)&adv},
+ {&routes, routes_cnt * sizeof(*routes)},
+ {&dns, (dns_cnt) ? sizeof(dns) : 0},
+ {dns_addr, dns_cnt * sizeof(*dns_addr)},
+ {search, search->len * 8}};
+ struct sockaddr_in6 all_nodes = {AF_INET6, 0, 0, ALL_IPV6_NODES, 0};
+ odhcpd_send(router_event.uloop.fd,
+ &all_nodes, iov, ARRAY_SIZE(iov), iface);
+
+ // Rearm timer
+ int msecs;
+ odhcpd_urandom(&msecs, sizeof(msecs));
+ msecs = (labs(msecs) % (1000 * (MaxRtrAdvInterval
+ - MinRtrAdvInterval))) + (MinRtrAdvInterval * 1000);
+ uloop_timeout_set(&iface->timer_rs, msecs);
+}
+
+
+// Forward router solicitation
+static void forward_router_solicitation(const struct interface *iface)
+{
+ if (!iface)
+ return;
+
+ struct icmp6_hdr rs = {ND_ROUTER_SOLICIT, 0, 0, {{0}}};
+ struct iovec iov = {&rs, sizeof(rs)};
+ struct sockaddr_in6 all_routers =
+ {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, iface->ifindex};
+
+ syslog(LOG_NOTICE, "Sending RS to %s", iface->ifname);
+ odhcpd_send(router_event.uloop.fd, &all_routers, &iov, 1, iface);
+}
+
+
+// Handler for incoming router solicitations on slave interfaces
+static void forward_router_advertisement(uint8_t *data, size_t len)
+{
+ struct nd_router_advert *adv = (struct nd_router_advert *)data;
+
+ // Rewrite options
+ uint8_t *end = data + len;
+ uint8_t *mac_ptr = NULL;
+ struct in6_addr *dns_ptr = NULL;
+ size_t dns_count = 0;
+
+ struct icmpv6_opt *opt;
+ icmpv6_for_each_option(opt, &adv[1], end) {
+ if (opt->type == ND_OPT_SOURCE_LINKADDR) {
+ // Store address of source MAC-address
+ mac_ptr = opt->data;
+ } else if (opt->type == ND_OPT_RECURSIVE_DNS && opt->len > 1) {
+ // Check if we have to rewrite DNS
+ dns_ptr = (struct in6_addr*)&opt->data[6];
+ dns_count = (opt->len - 1) / 2;
+ }
+ }
+
+ syslog(LOG_NOTICE, "Got a RA");
+
+ // Indicate a proxy, however we don't follow the rest of RFC 4389 yet
+ adv->nd_ra_flags_reserved |= ND_RA_FLAG_PROXY;
+
+ // Forward advertisement to all slave interfaces
+ struct sockaddr_in6 all_nodes = {AF_INET6, 0, 0, ALL_IPV6_NODES, 0};
+ struct iovec iov = {data, len};
+
+ struct odhcpd_ipaddr addr;
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head) {
+ if (iface->ra != RELAYD_RELAY || iface->master)
+ continue;
+
+ // Fixup source hardware address option
+ if (mac_ptr)
+ odhcpd_get_mac(iface, mac_ptr);
+
+ // If we have to rewrite DNS entries
+ if (iface->always_rewrite_dns && dns_ptr && dns_count > 0) {
+ const struct in6_addr *rewrite = iface->dns;
+ size_t rewrite_cnt = iface->dns_cnt;
+
+ if (rewrite_cnt == 0) {
+ if (odhcpd_get_interface_addresses(iface->ifindex, &addr, 1) < 1)
+ continue; // Unable to comply
+
+ rewrite = &addr.addr;
+ rewrite_cnt = 1;
+ }
+
+ // Copy over any other addresses
+ for (size_t i = 0; i < dns_count; ++i) {
+ size_t j = (i < rewrite_cnt) ? i : rewrite_cnt - 1;
+ dns_ptr[i] = rewrite[j];
+ }
+ }
+
+ odhcpd_send(router_event.uloop.fd, &all_nodes, &iov, 1, iface);
+ }
+}
diff --git a/src/router.h b/src/router.h
new file mode 100644
index 0000000..1e8649c
--- /dev/null
+++ b/src/router.h
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2012-2013 Steven Barth <steven@midlink.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#pragma once
+#include <stdint.h>
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+
+struct icmpv6_opt {
+ uint8_t type;
+ uint8_t len;
+ uint8_t data[6];
+};
+
+
+#define icmpv6_for_each_option(opt, start, end)\
+ for (opt = (struct icmpv6_opt*)(start);\
+ (void*)(opt + 1) <= (void*)(end) && opt->len > 0 &&\
+ (void*)(opt + opt->len) <= (void*)(end); opt += opt->len)
+
+
+#define MaxRtrAdvInterval 600
+#define MinRtrAdvInterval (MaxRtrAdvInterval / 3)
+#define MaxValidTime 7200
+#define MaxPreferredTime (3 * MaxRtrAdvInterval)
+
+#define ND_RA_FLAG_PROXY 0x4
+#define ND_RA_PREF_HIGH (1 << 3)
+#define ND_RA_PREF_LOW (3 << 3)
diff --git a/src/ubus.c b/src/ubus.c
new file mode 100644
index 0000000..0b7ec41
--- /dev/null
+++ b/src/ubus.c
@@ -0,0 +1,314 @@
+#include <syslog.h>
+#include <libubus.h>
+#include <libubox/uloop.h>
+#include <arpa/inet.h>
+
+#include "odhcpd.h"
+#include "dhcpv6.h"
+#include "dhcpv4.h"
+
+
+static struct ubus_context *ubus = NULL;
+static struct ubus_subscriber netifd;
+static struct blob_buf b;
+static struct blob_attr *dump = NULL;
+static uint32_t objid = 0;
+
+
+static int handle_dhcpv4_leases(struct ubus_context *ctx, _unused struct ubus_object *obj,
+ struct ubus_request_data *req, _unused const char *method,
+ _unused struct blob_attr *msg)
+{
+ blob_buf_init(&b, 0);
+ void *a = blobmsg_open_table(&b, "device");
+ time_t now = odhcpd_time();
+
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head) {
+ if (iface->dhcpv4 != RELAYD_SERVER)
+ continue;
+
+ void *i = blobmsg_open_table(&b, iface->ifname);
+ void *j = blobmsg_open_array(&b, "leases");
+
+ struct dhcpv4_assignment *lease;
+ list_for_each_entry(lease, &iface->dhcpv4_assignments, head) {
+ if (lease->valid_until < now)
+ continue;
+
+ void *l = blobmsg_open_table(&b, NULL);
+
+ char *buf = blobmsg_alloc_string_buffer(&b, "mac", 13);
+ odhcpd_hexlify(buf, lease->hwaddr, sizeof(lease->hwaddr));
+ blobmsg_add_string_buffer(&b);
+
+ blobmsg_add_string(&b, "hostname", lease->hostname);
+
+ buf = blobmsg_alloc_string_buffer(&b, "ip", INET_ADDRSTRLEN);
+ struct in_addr addr = {htonl(lease->addr)};
+ inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN);
+ blobmsg_add_string_buffer(&b);
+
+ blobmsg_add_u32(&b, "valid", now - lease->valid_until);
+
+ blobmsg_close_table(&b, l);
+ }
+
+ blobmsg_close_array(&b, j);
+ blobmsg_close_table(&b, i);
+ }
+
+ blobmsg_close_table(&b, a);
+ ubus_send_reply(ctx, req, b.head);
+ return 0;
+}
+
+
+static int handle_dhcpv6_leases(_unused struct ubus_context *ctx, _unused struct ubus_object *obj,
+ _unused struct ubus_request_data *req, _unused const char *method,
+ _unused struct blob_attr *msg)
+{
+ blob_buf_init(&b, 0);
+ void *a = blobmsg_open_table(&b, "device");
+ time_t now = odhcpd_time();
+
+ struct interface *iface;
+ list_for_each_entry(iface, &interfaces, head) {
+ if (iface->dhcpv6 != RELAYD_SERVER)
+ continue;
+
+ void *i = blobmsg_open_table(&b, iface->ifname);
+ void *j = blobmsg_open_array(&b, "leases");
+
+ struct dhcpv6_assignment *lease;
+ list_for_each_entry(lease, &iface->ia_assignments, head) {
+ if (lease->valid_until < now)
+ continue;
+
+ void *l = blobmsg_open_table(&b, NULL);
+
+ char *buf = blobmsg_alloc_string_buffer(&b, "duid", 264);
+ odhcpd_hexlify(buf, lease->clid_data, lease->clid_len);
+ blobmsg_add_string_buffer(&b);
+
+ blobmsg_add_u32(&b, "iaid", ntohl(lease->iaid));
+ blobmsg_add_string(&b, "hostname", (lease->hostname) ? lease->hostname : "");
+ blobmsg_add_u32(&b, "assigned", lease->assigned);
+ blobmsg_add_u32(&b, "length", lease->length);
+
+ void *m = blobmsg_open_array(&b, "ipv6");
+ struct in6_addr addr;
+ for (size_t i = 0; i < iface->ia_addr_len; ++i) {
+ if (iface->ia_addr[i].prefix > 64)
+ continue;
+
+ addr = iface->ia_addr[i].addr;
+ if (lease->length == 128)
+ addr.s6_addr32[3] = htonl(lease->assigned);
+ else
+ addr.s6_addr32[1] |= htonl(lease->assigned);
+
+ char *c = blobmsg_alloc_string_buffer(&b, NULL, INET6_ADDRSTRLEN);
+ inet_ntop(AF_INET6, &addr, c, INET6_ADDRSTRLEN);
+ blobmsg_add_string_buffer(&b);
+ }
+ blobmsg_close_table(&b, m);
+
+ blobmsg_add_u32(&b, "valid", now - lease->valid_until);
+
+ blobmsg_close_table(&b, l);
+ }
+
+ blobmsg_close_array(&b, j);
+ blobmsg_close_table(&b, i);
+ }
+
+ blobmsg_close_table(&b, a);
+ ubus_send_reply(ctx, req, b.head);
+ return 0;
+}
+
+
+static struct ubus_method main_object_methods[] = {
+ {.name = "ipv4leases", .handler = handle_dhcpv4_leases},
+ {.name = "ipv6leases", .handler = handle_dhcpv6_leases},
+};
+
+static struct ubus_object_type main_object_type =
+ UBUS_OBJECT_TYPE("dhcp", main_object_methods);
+
+static struct ubus_object main_object = {
+ .name = "dhcp",
+ .type = &main_object_type,
+ .methods = main_object_methods,
+ .n_methods = ARRAY_SIZE(main_object_methods),
+};
+
+
+enum {
+ DUMP_ATTR_INTERFACE,
+ DUMP_ATTR_MAX
+};
+
+static const struct blobmsg_policy dump_attrs[DUMP_ATTR_MAX] = {
+ [DUMP_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY },
+};
+
+
+enum {
+ IFACE_ATTR_INTERFACE,
+ IFACE_ATTR_IFNAME,
+ IFACE_ATTR_UP,
+ IFACE_ATTR_DATA,
+ IFACE_ATTR_MAX,
+};
+
+static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
+ [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
+ [IFACE_ATTR_UP] = { .name = "up", .type = BLOBMSG_TYPE_BOOL },
+ [IFACE_ATTR_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
+};
+
+static void handle_dump(_unused struct ubus_request *req, _unused int type, struct blob_attr *msg)
+{
+ struct blob_attr *tb[DUMP_ATTR_INTERFACE];
+ blobmsg_parse(dump_attrs, DUMP_ATTR_MAX, tb, blob_data(msg), blob_len(msg));
+
+ if (!tb[DUMP_ATTR_INTERFACE])
+ return;
+
+ free(dump);
+ dump = blob_memdup(tb[DUMP_ATTR_INTERFACE]);
+ raise(SIGHUP);
+}
+
+
+static struct interface* find_interface(struct blob_attr *msg)
+{
+ struct blob_attr *tb[IFACE_ATTR_MAX];
+ blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blob_data(msg), blob_len(msg));
+
+ const char *interface = (tb[IFACE_ATTR_INTERFACE]) ?
+ blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]) : "";
+ const char *ifname = (tb[IFACE_ATTR_IFNAME]) ?
+ blobmsg_get_string(tb[IFACE_ATTR_IFNAME]) : "";
+
+ struct interface *c;
+ list_for_each_entry(c, &interfaces, head)
+ if (!strcmp(interface, c->name) || !strcmp(ifname, c->ifname))
+ return c;
+
+ return NULL;
+}
+
+
+static int handle_update(_unused struct ubus_context *ctx, _unused struct ubus_object *obj,
+ _unused struct ubus_request_data *req, _unused const char *method,
+ struct blob_attr *msg)
+{
+ struct interface *iface = find_interface(msg);
+ if (iface && iface->ignore)
+ return 0;
+
+ ubus_invoke(ubus, objid, "dump", NULL, handle_dump, NULL, 0);
+ return 0;
+}
+
+
+static void subscribe_netifd(void)
+{
+ netifd.cb = handle_update;
+ ubus_subscribe(ubus, &netifd, objid);
+ ubus_invoke(ubus, objid, "dump", NULL, handle_dump, NULL, 0);
+}
+
+
+void ubus_apply_network(void)
+{
+ struct blob_attr *c;
+ int rem;
+
+ if (!dump)
+ return;
+
+ blobmsg_for_each_attr(c, dump, rem) {
+ struct interface *iface = find_interface(c);
+ if (!iface || !iface->ignore)
+ config_parse_interface(c, NULL);
+ }
+}
+
+
+enum {
+ OBJ_ATTR_ID,
+ OBJ_ATTR_PATH,
+ OBJ_ATTR_MAX
+};
+
+static const struct blobmsg_policy obj_attrs[OBJ_ATTR_MAX] = {
+ [OBJ_ATTR_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
+ [OBJ_ATTR_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING },
+};
+
+
+static void handle_event(_unused struct ubus_context *ctx, _unused struct ubus_event_handler *ev,
+ _unused const char *type, struct blob_attr *msg)
+{
+ struct blob_attr *tb[OBJ_ATTR_MAX];
+ blobmsg_parse(obj_attrs, OBJ_ATTR_MAX, tb, blob_data(msg), blob_len(msg));
+ objid = 0;
+
+ if (!tb[OBJ_ATTR_ID] || !tb[OBJ_ATTR_PATH])
+ return;
+
+ if (strcmp(blobmsg_get_string(tb[OBJ_ATTR_PATH]), "network.interface"))
+ return;
+
+ objid = blobmsg_get_u32(tb[OBJ_ATTR_ID]);
+ subscribe_netifd();
+}
+
+static struct ubus_event_handler event_handler = { .cb = handle_event };
+
+
+const char* ubus_get_ifname(const char *name)
+{
+ struct blob_attr *c;
+ int rem;
+
+ if (!dump)
+ return NULL;
+
+ blobmsg_for_each_attr(c, dump, rem) {
+ struct blob_attr *tb[IFACE_ATTR_MAX];
+ blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blob_data(c), blob_len(c));
+
+ if (!tb[IFACE_ATTR_INTERFACE] || strcmp(name,
+ blobmsg_get_string(tb[IFACE_ATTR_INTERFACE])))
+ continue;
+
+ if (tb[IFACE_ATTR_IFNAME])
+ return blobmsg_get_string(tb[IFACE_ATTR_IFNAME]);
+ }
+
+ return NULL;
+}
+
+
+int init_ubus(void)
+{
+ if (!(ubus = ubus_connect(NULL))) {
+ syslog(LOG_ERR, "Unable to connect to ubus: %s", strerror(errno));
+ return -1;
+ }
+
+ ubus_add_uloop(ubus);
+ ubus_add_object(ubus, &main_object);
+ ubus_register_event_handler(ubus, &event_handler, "ubus.object.add");
+ if (!ubus_lookup_id(ubus, "network.interface", &objid))
+ subscribe_netifd();
+
+ return 0;
+}
+