From 141f39f20a613e6c84271717c3db7fe2c981ecca Mon Sep 17 00:00:00 2001 From: Daniel Adolfsson Date: Wed, 11 Dec 2019 11:34:34 +0100 Subject: [PATCH] Initial commit --- .clang-format | 6 + .clang-tidy | 70 ++++++ .gitignore | 3 + CMakeLists.txt | 9 + ChangeLog | 103 +++++++++ Makefile | 35 +++ README.md | 31 +++ ndppd.8.adoc | 36 +++ ndppd.conf-dist | 30 +++ ndppd.conf.5.adoc | 107 +++++++++ ndppd.service | 11 + src/addr.c | 108 +++++++++ src/addr.h | 31 +++ src/alloc.c | 92 ++++++++ src/alloc.h | 28 +++ src/conf.c | 563 ++++++++++++++++++++++++++++++++++++++++++++++ src/conf.h | 27 +++ src/iface.c | 522 ++++++++++++++++++++++++++++++++++++++++++ src/iface.h | 54 +++++ src/log.c | 83 +++++++ src/log.h | 47 ++++ src/ndppd.c | 236 +++++++++++++++++++ src/ndppd.h | 92 ++++++++ src/neigh.c | 45 ++++ src/neigh.h | 65 ++++++ src/proxy.c | 222 ++++++++++++++++++ src/proxy.h | 44 ++++ src/rtnl.c | 310 +++++++++++++++++++++++++ src/rtnl.h | 54 +++++ src/rule.c | 28 +++ src/rule.h | 40 ++++ src/sio.c | 271 ++++++++++++++++++++++ src/sio.h | 43 ++++ 33 files changed, 3446 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 ChangeLog create mode 100644 Makefile create mode 100644 README.md create mode 100644 ndppd.8.adoc create mode 100644 ndppd.conf-dist create mode 100644 ndppd.conf.5.adoc create mode 100644 ndppd.service create mode 100644 src/addr.c create mode 100644 src/addr.h create mode 100644 src/alloc.c create mode 100644 src/alloc.h create mode 100644 src/conf.c create mode 100644 src/conf.h create mode 100644 src/iface.c create mode 100644 src/iface.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/ndppd.c create mode 100644 src/ndppd.h create mode 100644 src/neigh.c create mode 100644 src/neigh.h create mode 100644 src/proxy.c create mode 100644 src/proxy.h create mode 100644 src/rtnl.c create mode 100644 src/rtnl.h create mode 100644 src/rule.c create mode 100644 src/rule.h create mode 100644 src/sio.c create mode 100644 src/sio.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4671ac6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +BreakBeforeBraces: Allman +IndentWidth: 4 +ColumnLimit: 120 +AllowShortFunctionsOnASingleLine: None +Cpp11BracedListStyle: false +IndentPPDirectives: AfterHash diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..f96fce2 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,70 @@ +Checks: > + *, + -android-*, + -bugprone-bool-pointer-implicit-conversion, + -bugprone-exception-escape, + -cert-dcl16-c, + -cert-dcl50-cpp, + -cert-dcl59-cpp, + -cert-env33-c, + -clang-analyzer-*, + -clang-diagnostic-*, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-no-malloc, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-const-cast, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + -fuchsia-*, + -google-*, + -hicpp-avoid-c-arrays, + -hicpp-avoid-goto, + -hicpp-braces-around-statements, + -hicpp-function-size, + -hicpp-named-parameter, + -hicpp-no-array-decay, + -hicpp-no-assembler, + -hicpp-no-malloc, + -hicpp-signed-bitwise, + -hicpp-special-member-functions, + -hicpp-uppercase-literal-suffix, + -hicpp-vararg, + -llvm-*, + -misc-bool-pointer-implicit-conversion, + -misc-definitions-in-headers, + -misc-non-private-member-variables-in-classes, + -misc-unused-alias-decls, + -misc-unused-parameters, + -misc-unused-using-decls, + -modernize-avoid-c-arrays, + -modernize-use-default-member-init, + -modernize-use-trailing-return-type, + -modernize-use-using, + -objc-*, + -openmp-exception-escape, + -readability-braces-around-statements, + -readability-else-after-return, + -readability-function-size, + -readability-identifier-naming, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + -readability-named-parameter, + -readability-redundant-member-init, + -readability-redundant-preprocessor, + -readability-simplify-boolean-expr, + -readability-uppercase-literal-suffix, + -zircon-*, + google-default-arguments, + google-explicit-constructor, + google-runtime-operator \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de780c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +cmake-build-* +ndppd.conf diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..90aae28 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.10) +project(ndppd C) +set(CMAKE_C_STANDARD 99) + +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC) + +add_executable(ndppd ${SRC}) +target_compile_options(ndppd PRIVATE -Werror -Wall -Wextra) + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e24aec0 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,103 @@ +2019-12-xx Daniel Adolfsson + + * Version 1.0.0 + + * ndppd has been rewritten from scratch, and is now pure C. This should + be mostly compatible with the latest release 0.2.5, but several + contributions have been backported from master. + + * Support for epoll has been added for better scaling. It's still + possible to revert to using poll by defining NDPPD_NO_USE_EPOLL. + + * Managing routes and keeping track of local addresses are now done + through the use of Netlink. As such, /proc/* support is now gone. + + * + + +2017-01-07 Johnathan Sharratt + + * Version 0.2.6 + + * Added a new configuration setting named "deadtime" which allows + sessions that never made it the VALID to have a different (i.e. + shorter) life before they are removed (and potentially retried) + (defauilt is the same value as usual TTL for backwards compatibility) + + * Added a new configuration setting named "autowire" in the proxy + section (default is off) + + * If the "autowire" setting is on, then upon receiving a NDP + Neighbor Advertisment from one of the rule interfaces, a route will + be automatically added into the linux IP routing tables thus allowing + for a full featured gateway when IPv6 forwarding is turned on. + Note: Be careful as "accept_ra" may need to be set to 2 on the + interface during testing for the routing tables to retain their + default route (unrelated to this patch but took me a while to + discover). + + * When a session ends then anything that was "autowired" will be + automatically removed thus ensuring the routing tables are in a + similar state to before the daemon (or session) made any changes + + * Added a feature where the session will attempt to renew itself + (with a new NDP Solicitation) before it self-terminates, this is + required otherwise packets could be lost when the session terminates + triggering the automatically removal of the route table entry. + + * Ensured that renew operations only take place if the session has + been recently touched by an external solicitation - this ensures + that sessions that become IDLE are cleaned up quickly + + * Moved the daemonizing step till after the system executed the + configure step so that the error exit codes are returned to the daemon + caller. + + * No longer continuing to load the daemon if any of the interfaces fail + to load which should give a more predictable behaviour and better user experience. + +2016-04-18 Daniel Adolfsson + + * Version 0.2.5 + + * Defer configuration of interfaces until after daemonized; fixes an + issue where ndppd would fail to set ALLMULTI on the interface + properly. + + * Fix a cast so ndppd can be compiled on GCC 6. + + * Fix so ndppd changes working directory to / and umask to 0 once + daemonized. + +2015-10-13 Daniel Adolfsson + + * Version 0.2.4 + + * Fix an issue where ndppd daemonizes too early. + + * Fix to make sure the right pid is written to the pidfile. + +2012-09-21 Daniel Adolfsson + + * Version 0.2.3 + +2012-02-06 Daniel Adolfsson + + * Version 0.2.2 + + * Removed "libconfuse" dependency. + + * New "auto" configuration to detect outgoing interface, for forwarding + Neighbor Solicitation Messages. + + * Improved logging. + + * Bug fixes related to memory management. + +2012-01-26 Daniel Adolfsson + + * Author changed e-mail address; updated copyright info. + +2011-10-11 Daniel Adolfsson + + * Initial Release; 0.2.1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..528f607 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +ifdef DEBUG +CCFLAGS ?= -g -DDEBUG +else +CCFLAGS ?= -Os +endif + +PREFIX ?= /usr/local +CCC ?= gcc +GZIP ?= /bin/gzip +MANDIR ?= ${DESTDIR}${PREFIX}/share/man +SBINDIR ?= ${DESTDIR}${PREFIX}/sbin +ASCIIDOCTOR ?= /usr/bin/asciidoctor + +OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c)) + +all: ndppd ndppd.8.gz ndppd.conf.5.gz + +install: all + mkdir -p ${SBINDIR} ${MANDIR} ${MANDIR}/man1 ${MANDIR}/man5 + cp ndppd ${SBINDIR} + chmod +x ${SBINDIR}/ndppd + cp ndppd.8.gz ${MANDIR}/man1 + cp ndppd.conf.5.gz ${MANDIR}/man5 + +%.gz: %.adoc + ${ASCIIDOCTOR} -b manpage $< -o - | ${GZIP} > $@ + +ndppd: ${OBJS} + ${CC} -o ndppd ${LDFLAGS} ${OBJS} ${LIBS} + +%.o: %.c + ${CC} -c ${CPPFLAGS} $(CCFLAGS) -o $@ $< + +clean: + rm -f ndppd ndppd.conf.5.gz ndppd.8.gz ${OBJS} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b22636c --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# NDPPD + +Please read the manpages [ndppd.conf.5](ndppd.conf.5.adoc) and [ndppd.8](ndppd.8.adoc). + +## To do + +### In progress + +- [x] EPOLL support +- [x] rtnetlink: Tracking routes +- [x] rtnetlink: Tracking local addresses +- [ ] rtnetlink: Memory cleanup +- [ ] rtnetlink: Managing routes +- [x] Automatic detection of internal interfaces (auto) +- [ ] Automatically managing routes (autowire/autovia) +- [x] IPv6/ICMPv6 packet validation +- [ ] Reloading through SIGHUP +- [x] Configuration engine +- [x] Forwarding of Neighbor Solicitation messages +- [x] Forwarding of Neighbor Advertisement messages +- [x] Daemonization +- [x] Locking pidfiles +- [x] Syslog +- [x] Custom memory management (*nd_alloc*) +- [ ] Expiration of nd_neigh_t objects +- [ ] Refreshing nd_neigh_t if needed +- [x] Set and restore PROMISC and ALLMULTI + +### Undecided +- [ ] Control socket +- [ ] Cleaning up pidfiles diff --git a/ndppd.8.adoc b/ndppd.8.adoc new file mode 100644 index 0000000..9b72cc6 --- /dev/null +++ b/ndppd.8.adoc @@ -0,0 +1,36 @@ += ndppd(8) +Daniel Adolfsson +:doctype: manpage + +== Name +ndppd - neighbor advertisement proxy daemon for IPv6 + +== Synopsis +*ndppd* [ *-dv* ] [ *-c* _config_file_ ] [ *-p* _pid_file_ ] [ *--syslog* ] + +== Description +*ndppd* is a neighbor advertisement proxy daemon; it allows a host to proxy neighbor discovery +messages between interfaces to allow routing of otherwise non-routable IPv6 addresses. +It implements part of *RFC 4861*. + +== Options + +*-c*, *--config*=_path_:: + Location of the configuration file. ++ +Default: /etc/ndppd.conf + +*-p*, *--pidfile*=_path_:: + Path to the pidfile. Used in conjunction with *-d* or *--daemon*. + +*-d*, *--daemon*:: + Puts the daemon into the background. If *-p* or *--pidfile* has been provided, *ndppd* will lock the + specified file and write the PID of the newly spawned child to it. The file remains locked until the + child terminates. + +*--syslog*:: + Force the use of syslog even if *ndppd* is not running as a daemon. + +*-v*:: + Increase the verbosity of the logging. Multiple *-v* increases the verbosity even further. + diff --git a/ndppd.conf-dist b/ndppd.conf-dist new file mode 100644 index 0000000..590b52d --- /dev/null +++ b/ndppd.conf-dist @@ -0,0 +1,30 @@ +# Please refer to man ndppd.conf(8) for details. + +# invalid-ttl +#invalid-ttl 5000 + +# valid-ttl +#valid-ttl 30000 + +# renew +#renew 5000 + +# retransmit-limit +#retransmit-limit 3 + +# retransmit-time +#retransmit-time 1000 + +# keepalive +#keepalive yes + +# proxy +proxy eth0 { + # router + #router yes + + # rule [/] + rule dead::beef:: { + #auto + } +} diff --git a/ndppd.conf.5.adoc b/ndppd.conf.5.adoc new file mode 100644 index 0000000..b347568 --- /dev/null +++ b/ndppd.conf.5.adoc @@ -0,0 +1,107 @@ += ndppd.conf(5) +Daniel Adolfsson +:doctype: manpage + +== Name +ndppd.conf - ndppd configuration file + +== Description + +This file is used to describe which interfaces to proxy as well as which rules must match in order to respond to any _neighbor solicitation_ messages. +Most of the configuration options are simple key-value pairs, with the exceptions being *proxy* and *rule* which must also include a block containing +additional configuration options. + +A most basic example of this is *valid-ttl* with a configured value of 10000 milliseconds. + + valid-ttl 10000 + +Which options are valid depends on the block in which they are defined. The example above is allowed at _top level_, but would not be allowed +inside a *rule* or a *proxy* block. Please see _Options_ for details regarding valid configuration options. In order for the *ndppd.conf* to be valid, +at least one *proxy* must be defined. An in each of these proxies, at least one *rule* must be defined. + +In short; the general structure of *ndppd.conf* can be simplified to: + +[source] +---- +... + +route eth0 { + rule dead:beef:: { + ... + } +} +---- + +=== Comments + +*ndppd.conf* supports two types of comments. + +C-style + + /* This is a comment */ + +Python + + # This a comment + +== Options + +=== Top-level + +*valid-ttl* _milliseconds_:: + The time a target will be considered valid after having received a _neighbor advertisement_ from a neighbor. ++ +Default: 30000 + +*invalid-ttl* _milliseconds_:: + The time a target will be considered invalid after not receiving any _neighbor solicitation_ messages from a neighbor. ++ +Default: 5000 + +*retrans-time* _milliseconds_:: + The time *ndppd* will wait before sending another _neighbor solicitation_ to the internal interface. ++ +Default: 1000 + +*retrans-limit* _count_:: + How many times *ndppd* attempt to send _neighbor solicitation_ messages, and not receiving a valid _neighbor advertisement_ response, + before considering it being invalid. ++ +Default: 3 + +*proxy* _interface_ _block_:: + Create a new proxy on the specified interface. That interface will be listening for _neighbor solicitation_ + messages and then reply with _neighbor advertisement_ messages if the conditions were met. + + proxy eth0 { + # Proxy specific configuration + } + + +=== Proxy specific + +*rule* _ip_ [ */* _prefix_ ] _block_:: + Add a new rule for the matching IPv6 address. If *prefix* is not specified, it defaults to 128. Note that the + address and prefix must be provided without any whitespace between them. + + rule dead:beef::1/127 { + # Route specific configuration + } + +=== Rule specific + +auto:: + If specified, *ndppd* will attempt to automatically determine where to forward _Neighbor Solicitation_ messages. + This feature uses the *Netlink* protocol. + +static:: + Automatically respond. *This option is mutually exclusive with iface and auto*. + +*iface* _interface_:: + Forwards the *Neighbor Solicitation* message through this specific interface. + +*autowire*:: + A flag whether or not a new route should be automatically added to the routing table if a match has been found. + +*table* _index_:: + Indicates which routing table should be used when *auto* and *autowire* is used. diff --git a/ndppd.service b/ndppd.service new file mode 100644 index 0000000..aacb6f2 --- /dev/null +++ b/ndppd.service @@ -0,0 +1,11 @@ +[Unit] +Description=NDP Proxy Daemon +After=network.target + +[Service] +ExecStart=/usr/sbin/ndppd -d -p /var/run/ndppd/ndppd.pid +Type=forking +PIDFile=/var/run/ndppd/ndppd.pid + +[Install] +WantedBy=multi-user.target diff --git a/src/addr.c b/src/addr.c new file mode 100644 index 0000000..c6e5950 --- /dev/null +++ b/src/addr.c @@ -0,0 +1,108 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include + +#include "ndppd.h" + +/*! Get the string representation of the specified address. + * + * @note This function returns a pointer to static data. It uses three different static arrays + * to allow the function to be chained. + * + * @param addr + * @return + */ +const char *nd_addr_to_string(nd_addr_t *addr) +{ + static int index; + static char buf[3][64]; + + if (addr == NULL) + return "(null)"; + + int n = index++ % 3; + + return inet_ntop(AF_INET6, addr, buf[n], sizeof(buf[n])); +} + +/*! Returns true if the specified address is a multicast address. */ +bool nd_addr_is_multicast(nd_addr_t *addr) +{ + return addr->s6_addr[0] == 0xff; +} + +bool nd_addr_is_unicast(nd_addr_t *addr) +{ + return !(addr->s6_addr32[2] == 0 && addr->s6_addr32[3] == 0) && addr->s6_addr[0] != 0xff; +} + +/*! Compares two addresses using the specified prefix length. + * + * @param first + * @param second + * @param pflen + * @return true if there is a match + */ +bool nd_addr_match(nd_addr_t *first, nd_addr_t *second, int pflen) +{ + if (pflen < 0 || pflen > 128) + return false; + + if (pflen == 0) + return true; + + if (pflen == 128) + return first->s6_addr32[0] == second->s6_addr32[0] && first->s6_addr32[1] == second->s6_addr32[1] && + first->s6_addr32[2] == second->s6_addr32[2] && first->s6_addr32[3] == second->s6_addr32[3]; + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + const uint32_t masks[] = { + 0x80000000, 0xc0000000, 0xe0000000, 0xf0000000, 0xf8000000, 0xfc000000, 0xfe000000, 0xff000000, + 0xff800000, 0xffc00000, 0xffe00000, 0xfff00000, 0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000, + 0xffff8000, 0xffffc000, 0xffffe000, 0xfffff000, 0xfffff800, 0xfffffc00, 0xfffffe00, 0xffffff00, + 0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8, 0xfffffffc, 0xfffffffe, 0xffffffff, + }; +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + const uint32_t masks[] = { + 0x00000080, 0x000000c0, 0x000000e0, 0x000000f0, 0x000000f8, 0x000000fc, 0x000000fe, 0x000000ff, + 0x000080ff, 0x0000c0ff, 0x0000e0ff, 0x0000f0ff, 0x0000f8ff, 0x0000fcff, 0x0000feff, 0x0000ffff, + 0x0080ffff, 0x00c0ffff, 0x00e0ffff, 0x00f0ffff, 0x00f8ffff, 0x00fcffff, 0x00feffff, 0x00ffffff, + 0x80ffffff, 0xc0ffffff, 0xe0ffffff, 0xf0ffffff, 0xf8ffffff, 0xfcffffff, 0xfeffffff, 0xffffffff, + }; +#else +# error __BYTE_ORDER__ is not defined +#endif + + for (unsigned int i = 0, top = (unsigned int)(pflen - 1) >> 5U; i <= top; i++) + { + uint32_t mask = i < top ? 0xffffffff : masks[(unsigned int)(pflen - 1) & 31U]; + + if ((first->s6_addr32[i] ^ second->s6_addr32[i]) & mask) + return false; + } + + return true; +} + +bool nd_addr_eq(nd_addr_t *first, nd_addr_t *second) +{ + return first->s6_addr32[0] == second->s6_addr32[0] && first->s6_addr32[1] == second->s6_addr32[1] && + first->s6_addr32[2] == second->s6_addr32[2] && first->s6_addr32[3] == second->s6_addr32[3]; +} diff --git a/src/addr.h b/src/addr.h new file mode 100644 index 0000000..1d4e58b --- /dev/null +++ b/src/addr.h @@ -0,0 +1,31 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_ADDR_H +#define NDPPD_ADDR_H + +#include "ndppd.h" + +bool nd_addr_is_multicast(nd_addr_t *addr); +bool nd_addr_is_unicast(nd_addr_t *addr); + +const char *nd_addr_to_string(nd_addr_t *addr); +bool nd_addr_match(nd_addr_t *first, nd_addr_t *second, int pflen); +bool nd_addr_eq(nd_addr_t *first, nd_addr_t *second); + +#endif /* NDPPD_ADDR_H */ diff --git a/src/alloc.c b/src/alloc.c new file mode 100644 index 0000000..85baa5c --- /dev/null +++ b/src/alloc.c @@ -0,0 +1,92 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include + +#ifndef NDPPD_ALLOC_SIZE +# define NDPPD_ALLOC_SIZE 16384 +#endif + +#include "ndppd.h" +#include "alloc.h" + +typedef struct ndL_chunk ndL_chunk_t; + +struct ndL_chunk +{ + ndL_chunk_t *next; + size_t free; + size_t size; +}; + +static ndL_chunk_t *ndL_chunks; +static size_t ndL_alloc_size = NDPPD_ALLOC_SIZE; + +char *nd_strdup(const char *str) +{ + size_t len = strlen(str); + char *buf = (char *)nd_alloc(len + 1); + strcpy(buf, str); + return buf; +} + +void *nd_alloc(size_t size) +{ + assert(size > 0 && size < 512); + + /* To keep everything properly aligned, we'll make sure it's multiple of 8. */ + size = (size + 7U) & ~7U; + + for (ndL_chunk_t *chunk = ndL_chunks; chunk; chunk = chunk->next) + { + if (chunk->free >= size) + { + void *ptr = (void *)chunk + chunk->size - chunk->free; + chunk->free -= size; + return ptr; + } + } + + ndL_chunk_t *chunk = (ndL_chunk_t *)calloc(1, ndL_alloc_size); + + /* Should never happen. */ + if (chunk == NULL) + abort(); + + chunk->next = ndL_chunks; + chunk->size = ndL_alloc_size; + chunk->free = ndL_alloc_size - ((sizeof(ndL_chunk_t) + 7U) & ~7U); + + ndL_chunks = chunk; + + ndL_alloc_size *= 2; + + void *ptr = (void *)chunk + chunk->size - chunk->free; + chunk->free -= size; + return ptr; +} + +void nd_alloc_cleanup() +{ + ND_LL_FOREACH_S(ndL_chunks, chunk, tmp, next) + { + free(chunk); + } +} diff --git a/src/alloc.h b/src/alloc.h new file mode 100644 index 0000000..88fad5d --- /dev/null +++ b/src/alloc.h @@ -0,0 +1,28 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_ALLOC_H +#define NDPPD_ALLOC_H + +void *nd_alloc(size_t size); +char *nd_strdup(const char *str); +void nd_alloc_cleanup(); + +#define ND_ALLOC(type) (type *)nd_alloc(sizeof(type)) + +#endif /* NDPPD_ALLOC_H */ diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000..2e3546c --- /dev/null +++ b/src/conf.c @@ -0,0 +1,563 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef INT_MAX +# define INT_MAX __INT_MAX__ +#endif + +#ifndef INT_MIN +# define INT_MIN (-INT_MAX - 1) +#endif + +#include "conf.h" +#include "ndppd.h" +#include "proxy.h" +#include "rule.h" + +int nd_conf_invalid_ttl = 10000; +int nd_conf_valid_ttl = 10000; +int nd_conf_renew = 5000; +int nd_conf_retrans_limit = 3; +int nd_conf_retrans_time = 1000; +bool nd_conf_keepalive = false; + +typedef struct +{ + const char *data; + size_t offset; + size_t length; + int line; + int column; +} ndL_state_t; + +typedef bool (*ndL_cfcb_t)(ndL_state_t *state, void *ptr); + +typedef struct +{ + const char *key; + int scope; + int type; + uintptr_t offset; + int min; + int max; + ndL_cfcb_t cb; +} ndL_cfinfo_t; + +/* Scopes. */ +enum +{ + NDL_DEFAULT, + NDL_PROXY, + NDL_ROUTE +}; + +/* Configuration types. */ +enum +{ + NDL_NONE, + NDL_INT, + NDL_BOOL, + NDL_ADDR +}; + +/* Character classes. */ +enum +{ + NDL_ALPHA = 256, /* [a-zA-Z] */ + NDL_ALNUM, /* [a-zA-Z0-9] */ + NDL_DIGIT, /* [0-9] */ + NDL_EALNM, /* [a-zA-Z0-9_-] */ + NDL_SPACE, /* [\s] */ + NDL_SNONL, /* [^\S\n] */ + NDL_XNONL, /* [^\n] */ + NDL_IPV6X, /* [A-Fa-f0-9:] */ +}; + +static bool ndL_parse_rule(ndL_state_t *state, nd_proxy_t *proxy); +static bool ndL_parse_proxy(ndL_state_t *state, void *unused); + +static const ndL_cfinfo_t ndL_cfinfo_table[] = { + { "proxy", NDL_DEFAULT, NDL_NONE, 0, 0, 0, (ndL_cfcb_t)ndL_parse_proxy }, + { "rule", NDL_PROXY, NDL_NONE, 0, 0, 0, (ndL_cfcb_t)ndL_parse_rule }, + { "invalid-ttl", NDL_DEFAULT, NDL_INT, (uintptr_t)&nd_conf_invalid_ttl, 1000, 3600000, NULL }, + { "valid-ttl", NDL_DEFAULT, NDL_INT, (uintptr_t)&nd_conf_valid_ttl, 10000, 3600000, NULL }, + { "renew", NDL_DEFAULT, NDL_INT, (uintptr_t)&nd_conf_renew, 0, 0, NULL }, + { "retrans-limit", NDL_DEFAULT, NDL_INT, (uintptr_t)&nd_conf_retrans_limit, 0, 10, NULL }, + { "retrans-time", NDL_DEFAULT, NDL_INT, (uintptr_t)&nd_conf_retrans_time, 0, 60000, NULL }, + { "keepalive", NDL_DEFAULT, NDL_BOOL, (uintptr_t)&nd_conf_keepalive, 0, 0, NULL }, + { "router", NDL_PROXY, NDL_BOOL, offsetof(nd_proxy_t, router), 0, 0, NULL }, + { "auto", NDL_ROUTE, NDL_BOOL, offsetof(nd_rule_t, is_auto), 0, 0, NULL }, + { "promisc", NDL_PROXY, NDL_BOOL, offsetof(nd_proxy_t, promisc), 0, 0, NULL }, + { NULL, 0, 0, 0, 0, 0, NULL }, +}; + +static void ndL_error(const ndL_state_t *state, const char *fmt, ...) +{ + char buf[512]; + va_list va; + + va_start(va, fmt); + vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + + nd_log_error("%s at line %d column %d", buf, state->line, state->column); +} + +static char ndL_accept_one(ndL_state_t *state, int cl) +{ + if (state->offset >= state->length) + return 0; + + char ch = state->data[state->offset]; + + bool result; + + switch (cl) + { + case 0: + result = true; + break; + + case NDL_ALPHA: + result = isalpha(ch); + break; + + case NDL_ALNUM: + result = isalnum(ch); + break; + + case NDL_DIGIT: + result = isdigit(ch); + break; + + case NDL_EALNM: + result = isalnum(ch) || ch == '_' || ch == '-'; + break; + + case NDL_SPACE: + result = isspace(ch); + break; + + case NDL_SNONL: + result = isspace(ch) && ch != '\n'; + break; + + case NDL_XNONL: + result = ch != '\n'; + break; + + case NDL_IPV6X: + result = isxdigit(ch) || ch == ':'; + break; + + default: + result = (cl >= 0 && cl < 256) && (unsigned char)ch == cl; + break; + } + + if (!result) + return false; + + if (ch == '\n') + { + state->line++; + state->column = 1; + } + else + { + state->column++; + } + + state->offset++; + + return ch; +} + +static bool ndL_accept_all(ndL_state_t *state, int cl, char *buf, size_t buflen) +{ + ndL_state_t tmp = *state; + + for (size_t i = 0; !buf || i < buflen; i++) + { + char ch = ndL_accept_one(&tmp, cl); + + if (buf) + buf[i] = ch; + + if (!ch) + { + if (i > 0) + { + *state = tmp; + return true; + } + + break; + } + } + + return false; +} + +static bool ndL_accept(ndL_state_t *state, const char *str, int except_cl) +{ + ndL_state_t tmp = *state; + + while (*str && ndL_accept_one(&tmp, *str)) + str++; + + if (*str) + return false; + + if (except_cl && ndL_accept_one(&tmp, except_cl)) + return false; + + *state = tmp; + return true; +} + +static bool ndL_accept_bool(ndL_state_t *state, bool *value) +{ + if (ndL_accept(state, "yes", NDL_EALNM) || ndL_accept(state, "true", NDL_EALNM)) + *value = true; + else if (ndL_accept(state, "no", NDL_EALNM) || ndL_accept(state, "false", NDL_EALNM)) + *value = false; + else + { + /* For accurate reporting of location. */ + ndL_state_t tmp = *state; + if (ndL_accept_one(&tmp, NDL_XNONL) != 0) + return false; + + *value = true; + } + + return true; +} + +static bool ndL_accept_int(ndL_state_t *state, int *value) +{ + ndL_state_t tmp = *state; + + int n = ndL_accept_one(&tmp, '-') ? -1 : 1; + + char buf[32]; + + if (!ndL_accept_all(&tmp, NDL_DIGIT, buf, sizeof(buf))) + return false; + + /* Trailing [A-Za-z0-9_-] are invalid. */ + if (ndL_accept_one(&tmp, NDL_EALNM)) + return false; + + long longval = strtoll(buf, NULL, 10) * n; + + if (longval < INT_MIN || longval > INT_MAX) + return false; + + *value = (int)longval; + *state = tmp; + return true; +} + +static bool ndL_eof(ndL_state_t *state) +{ + return state->offset >= state->length; +} + +static void ndL_skip(ndL_state_t *state, bool skip_newline) +{ + for (;;) + { + ndL_accept_all(state, skip_newline ? NDL_SPACE : NDL_SNONL, NULL, 0); + + if (ndL_accept(state, "#", 0)) + { + ndL_accept_all(state, NDL_XNONL, NULL, 0); + } + else if (ndL_accept(state, "/*", 0)) + { + for (;;) + { + if (ndL_eof(state)) + { + ndL_error(state, "Expected end-of-comment before end-of-file"); + break; + } + + if (ndL_accept(state, "*/", 0)) + break; + + ndL_accept_one(state, 0); + } + } + else + { + break; + } + } +} + +static bool ndL_accept_addr(ndL_state_t *state, nd_addr_t *addr) +{ + ndL_state_t tmp = *state; + + char buf[64]; + + for (size_t i = 0; i < sizeof(buf); i++) + { + if (!(buf[i] = ndL_accept_one(&tmp, NDL_IPV6X))) + { + if (i == 0) + return false; + + /* Make sure we don't have a trailing [A-Za-z0-9-_]. */ + if (ndL_accept_one(&tmp, NDL_EALNM)) + return false; + + if (inet_pton(AF_INET6, buf, addr) != 1) + { + ndL_error(state, "Invalid IPv6 address \"%s\"", buf); + return false; + } + + *state = tmp; + return true; + } + } + + return false; +} + +static bool ndL_parse_block(ndL_state_t *state, int scope, void *ptr); + +static bool ndL_parse_rule(ndL_state_t *state, nd_proxy_t *proxy) +{ + nd_rule_t *rule = nd_rule_create(proxy); + + if (!ndL_accept_addr(state, &rule->addr)) + { + ndL_error(state, "Expected IPv6 address"); + return false; + } + + if (ndL_accept(state, "/", 0)) + { + /* Just for accurate logging if there is an error. */ + ndL_state_t tmp = *state; + + if (!ndL_accept_int(state, &rule->prefix)) + { + ndL_error(state, "Expected prefix"); + return false; + } + + if (rule->prefix < 0 || rule->prefix > 128) + { + ndL_error(&tmp, "Invalid prefix (%d)", rule->prefix); + return false; + } + } + else + { + rule->prefix = 128; + } + + return ndL_parse_block(state, NDL_ROUTE, rule); +} + +static bool ndL_parse_proxy(ndL_state_t *state, __attribute__((unused)) void *unused) +{ + char ifname[IF_NAMESIZE]; + + if (!ndL_accept_all(state, NDL_EALNM, ifname, sizeof(ifname))) + { + ndL_error(state, "Expected interface name"); + return false; + } + + nd_proxy_t *proxy = nd_proxy_create(ifname); + + if (proxy == NULL) + return false; + + return ndL_parse_block(state, NDL_PROXY, proxy); +} + +static bool ndL_parse_block(ndL_state_t *state, int scope, void *ptr) +{ + ndL_skip(state, false); + + if (scope != NDL_DEFAULT && !ndL_accept_one(state, '{')) + { + ndL_error(state, "Expected start-of-block '{'"); + return false; + } + + for (;;) + { + ndL_skip(state, true); + + if (scope != NDL_DEFAULT && ndL_accept_one(state, '}')) + return true; + + if (ndL_eof(state)) + { + if (scope != NDL_DEFAULT) + { + ndL_error(state, "Expected end-of-block '}'"); + return false; + } + + return true; + } + + bool found = false; + + for (int i = 0; !found && ndL_cfinfo_table[i].key; i++) + { + if (ndL_cfinfo_table[i].scope != scope) + continue; + + if (!ndL_accept(state, ndL_cfinfo_table[i].key, NDL_EALNM)) + continue; + + ndL_skip(state, false); + + found = true; + + const ndL_cfinfo_t *t = &ndL_cfinfo_table[i]; + const ndL_state_t saved_state = *state; + + switch (ndL_cfinfo_table[i].type) + { + case NDL_NONE: + break; + + case NDL_BOOL: + if (!ndL_accept_bool(state, (bool *)(ptr + t->offset))) + { + ndL_error(&saved_state, "Expected boolean value"); + return false; + } + break; + + case NDL_INT: + if (!ndL_accept_int(state, (int *)(ptr + t->offset))) + { + ndL_error(&saved_state, "Expected an integer"); + return false; + } + if (*(int *)(ptr + t->offset) < t->min || *(int *)(ptr + t->offset) > t->max) + { + ndL_error(&saved_state, "Invalid range; must be between %d and %d", t->min, t->max); + return false; + } + break; + + case NDL_ADDR: + if (!ndL_accept_addr(state, (nd_addr_t *)(ptr + t->offset))) + { + ndL_error(&saved_state, "Expected an IPv6 address"); + return false; + } + break; + } + + if (t->cb) + { + ndL_skip(state, false); + + if (!t->cb(state, ptr)) + return false; + } + + ndL_skip(state, false); + + if (!ndL_eof(state) && !ndL_accept_one(state, '\n')) + { + ndL_error(state, "Expected newline"); + return false; + } + } + + if (!found) + { + ndL_error(state, "Invalid configuration"); + return false; + } + } +} + +bool nd_conf_load(const char *path) +{ + FILE *fp = fopen(path, "r"); + + if (fp == NULL) + { + nd_log_error("Failed to load configuration (%s): %s", path, strerror(errno)); + return NULL; + } + + struct stat stat; + + if (fstat(fileno(fp), &stat) < 0) + { + nd_log_error("Failed to determine size: %s", strerror(errno)); + fclose(fp); + return NULL; + } + + char *buf = (char *)malloc(stat.st_size); + + if (buf == NULL) + { + nd_log_error("Failed to allocate buffer: %s", strerror(errno)); + fclose(fp); + return NULL; + } + + bool result = false; + + if (fread(buf, stat.st_size, 1, fp) != 1) + { + nd_log_error("Failed to read config: %s", strerror(errno)); + } + else + { + ndL_state_t state = { .data = buf, .offset = 0, .length = stat.st_size, .column = 0, .line = 1 }; + + /* TODO: Validate configuration. */ + + result = ndL_parse_block(&state, NDL_DEFAULT, NULL); + } + + fclose(fp); + free(buf); + return result; +} diff --git a/src/conf.h b/src/conf.h new file mode 100644 index 0000000..9413d14 --- /dev/null +++ b/src/conf.h @@ -0,0 +1,27 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_CF_H +#define NDPPD_CF_H + +#include "ndppd.h" + +/* cf.c */ +bool nd_conf_load(const char *path); + +#endif // NDPPD_CF_H diff --git a/src/iface.c b/src/iface.c new file mode 100644 index 0000000..f616c81 --- /dev/null +++ b/src/iface.c @@ -0,0 +1,522 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addr.h" +#include "iface.h" +#include "ndppd.h" +#include "neigh.h" +#include "proxy.h" +#include "sio.h" + +extern int nd_conf_invalid_ttl; +extern int nd_conf_valid_ttl; +extern int nd_conf_renew; +extern int nd_conf_retrans_limit; +extern int nd_conf_retrans_time; +extern bool nd_conf_keepalive; + +static nd_iface_t *ndL_first_iface, *ndL_first_free_iface; + +bool nd_iface_no_restore_flags; + +typedef struct +{ + struct ip6_hdr ip6_hdr; + struct icmp6_hdr icmp6_hdr; +} ndL_icmp6_msg_t; + +static void ndL_handle_ns(nd_iface_t *ifa, ndL_icmp6_msg_t *msg) +{ + struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *)&msg->icmp6_hdr; + + if (msg->ip6_hdr.ip6_plen < sizeof(struct nd_neighbor_solicit)) + return; + + /* TODO: We need to properly parse options here. */ + + size_t optlen = ntohs(msg->ip6_hdr.ip6_plen) - sizeof(struct nd_neighbor_solicit); + + if (optlen < 8) + return; + + struct nd_opt_hdr *opt = (struct nd_opt_hdr *)((void *)ns + sizeof(struct nd_neighbor_solicit)); + + if (opt->nd_opt_len != 1 || opt->nd_opt_type != ND_OPT_SOURCE_LINKADDR) + return; + + uint8_t *lladdr = (uint8_t *)((void *)opt + 2); + + if (ifa->proxy) + nd_proxy_handle_ns(ifa->proxy, &msg->ip6_hdr.ip6_src, &msg->ip6_hdr.ip6_dst, &ns->nd_ns_target, lladdr); +} + +static void ndL_handle_na(nd_iface_t *iface, ndL_icmp6_msg_t *msg) +{ + if (msg->ip6_hdr.ip6_plen < sizeof(struct nd_neighbor_advert)) + return; + + struct nd_neighbor_advert *na = (struct nd_neighbor_advert *)&msg->icmp6_hdr; + + nd_neigh_t *neigh; + ND_LL_SEARCH(iface->neighs, neigh, next_in_iface, nd_addr_eq(&neigh->tgt, &na->nd_na_target)); + + if (!neigh) + return; + + neigh->state = ND_STATE_VALID; + neigh->ttl = nd_conf_valid_ttl; + neigh->touched_at = nd_current_time; +} + +static uint16_t ndL_calculate_checksum(uint32_t sum, const void *data, size_t length) +{ + uint8_t *p = (uint8_t *)data; + + for (size_t i = 0; i < length; i += 2) + { + if (i + 1 < length) + sum += ntohs(*(uint16_t *)p), p += 2; + else + sum += *p++; + + if (sum > 0xffff) + sum -= 0xffff; + } + + return sum; +} + +static uint16_t ndL_calculate_icmp6_checksum(ndL_icmp6_msg_t *msg, size_t size) +{ + /* IPv6 pseudo-header. */ + struct __attribute__((packed)) + { + struct in6_addr src; + struct in6_addr dst; + uint32_t len; + uint8_t unused[3]; + uint8_t type; + struct icmp6_hdr icmp6_hdr; + } hdr; + + hdr.src = msg->ip6_hdr.ip6_src; + hdr.dst = msg->ip6_hdr.ip6_dst; + hdr.len = htonl(size - sizeof(struct ip6_hdr)); + hdr.unused[0] = 0; + hdr.unused[1] = 0; + hdr.unused[2] = 0; + hdr.type = IPPROTO_ICMPV6; + hdr.icmp6_hdr = msg->icmp6_hdr; + hdr.icmp6_hdr.icmp6_cksum = 0; + + uint16_t sum; + sum = ndL_calculate_checksum(0xffff, &hdr, sizeof(hdr)); + sum = ndL_calculate_checksum(sum, (void *)msg + sizeof(ndL_icmp6_msg_t), size - sizeof(ndL_icmp6_msg_t)); + + return htons(~sum); +} + +static void ndL_handle_packet(nd_iface_t *iface, uint8_t *buf, size_t buflen) +{ + ndL_icmp6_msg_t *msg = (ndL_icmp6_msg_t *)buf; + + if ((size_t)buflen < sizeof(ndL_icmp6_msg_t)) + /* TODO: log. Invalid length. */ + return; + + if ((size_t)buflen != sizeof(struct ip6_hdr) + ntohs(msg->ip6_hdr.ip6_plen)) + /* TODO: log. Invalid length. */ + return; + + if (msg->ip6_hdr.ip6_nxt != IPPROTO_ICMPV6) + /* TODO: log. Invalid next header. */ + return; + + if (ndL_calculate_icmp6_checksum(msg, buflen) != msg->icmp6_hdr.icmp6_cksum) + /* TODO: log. Invalid checksum. */ + return; + + /* TODO: Validate checksum, lengths, etc. */ + + if (msg->icmp6_hdr.icmp6_type == ND_NEIGHBOR_SOLICIT) + ndL_handle_ns(iface, msg); + else if (msg->icmp6_hdr.icmp6_type == ND_NEIGHBOR_ADVERT) + ndL_handle_na(iface, msg); +} + +static void ndL_sio_handler(nd_sio_t *sio, __attribute__((unused)) int events) +{ + nd_iface_t *ifa = (nd_iface_t *)sio->data; + + struct sockaddr_ll lladdr; + memset(&lladdr, 0, sizeof(struct sockaddr_ll)); + lladdr.sll_family = AF_PACKET; + lladdr.sll_protocol = htons(ETH_P_IPV6); + lladdr.sll_ifindex = (int)ifa->index; + + uint8_t buf[1024]; + + for (;;) + { + ssize_t len = nd_sio_recv(sio, (struct sockaddr *)&lladdr, sizeof(lladdr), buf, sizeof(buf)); + + if (len == 0) + return; + + if (len < 0) + { + if (errno == EAGAIN) + return; + + /* TODO */ + return; + } + + ndL_handle_packet(ifa, buf, len); + } +} + +nd_iface_t *nd_iface_open(const char *name, unsigned int index) +{ + char tmp_name[IF_NAMESIZE]; + + if (!name && !index) + return NULL; + + if (name && index && if_nametoindex(name) != index) + { + nd_log_error("Expected interface %s to have index %d", name, index); + return NULL; + } + else if (name && !(index = if_nametoindex(name))) + { + nd_log_error("Failed to get index of interface %s: %s", name, strerror(errno)); + return NULL; + } + else if (!(name = if_indextoname(index, tmp_name))) + { + nd_log_error("Failed to get name of interface index %d: %s", index, strerror(errno)); + return NULL; + } + + /* If the specified interface is already opened, just increase the reference counter. */ + + nd_iface_t *iface; + ND_LL_SEARCH(ndL_first_iface, iface, next, iface->index == index); + + if (iface) + { + iface->refs++; + return iface; + } + + /* No such interface. */ + + nd_sio_t *sio = nd_sio_open(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)); + + if (!sio) + { + nd_log_error("Failed to create socket: %s", strerror(errno)); + return NULL; + } + + /* Determine link-layer address. */ + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, name); + + if (ioctl(sio->fd, SIOCGIFHWADDR, &ifr) < 0) + { + nd_sio_close(sio); + nd_log_error("Failed to determine link-layer address: %s", strerror(errno)); + return NULL; + } + + /* Set up filter, so we only get NS and NA messages. */ + + static struct sock_filter filter[] = { + /* Load next header field. */ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, offsetof(struct ip6_hdr, ip6_nxt)), + /* Bail if it's not IPPROTO_ICMPV6. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3), + /* Load the ICMPv6 type. */ + BPF_STMT(BPF_LD | BPF_B | BPF_ABS, sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type)), + /* Keep if ND_NEIGHBOR_SOLICIT. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0), + /* Keep if ND_NEIGHBOR_SOLICIT. */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), + /* Drop packet. */ + BPF_STMT(BPF_RET | BPF_K, 0), + /* Keep packet. */ + BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1) + }; + + static struct sock_fprog fprog = { 7, filter }; + + if (setsockopt(sio->fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) + { + nd_sio_close(sio); + nd_log_error("Failed to configure netfilter: %s", strerror(errno)); + return NULL; + } + + /* Allocate the nd_ifa_t object. */ + + iface = ndL_first_free_iface; + + if (iface != NULL) + ND_LL_DELETE(ndL_first_free_iface, iface, next); + else + iface = ND_ALLOC(nd_iface_t); + + memset(iface, 0, sizeof(nd_iface_t)); + + ND_LL_PREPEND(ndL_first_iface, iface, next); + + iface->sio = sio; + iface->index = index; + iface->refs = 1; + iface->old_allmulti = -1; + iface->old_promisc = -1; + strcpy(iface->name, name); + memcpy(iface->lladdr, ifr.ifr_hwaddr.sa_data, 6); + + sio->data = (intptr_t)iface; + sio->handler = ndL_sio_handler; + + nd_log_info("New interface %s (%d)", iface->name, iface->index); + + return iface; +} + +void nd_iface_close(nd_iface_t *iface) +{ + if (--iface->refs > 0) + return; + + if (!nd_iface_no_restore_flags) + { + if (iface->old_promisc >= 0) + nd_iface_set_promisc(iface, iface->old_promisc); + if (iface->old_allmulti >= 0) + nd_iface_set_allmulti(iface, iface->old_allmulti); + } + + nd_sio_close(iface->sio); + + ND_LL_DELETE(ndL_first_iface, iface, next); + ND_LL_PREPEND(ndL_first_free_iface, iface, next); +} + +void ndL_get_local_addr(nd_iface_t *iface, nd_addr_t *addr) +{ + addr->s6_addr[0] = 0xfe; + addr->s6_addr[1] = 0x80; + addr->s6_addr[8] = iface->lladdr[0] ^ 0x02U; + addr->s6_addr[9] = iface->lladdr[1]; + addr->s6_addr[10] = iface->lladdr[2]; + addr->s6_addr[11] = 0xff; + addr->s6_addr[12] = 0xfe; + addr->s6_addr[13] = iface->lladdr[3]; + addr->s6_addr[14] = iface->lladdr[4]; + addr->s6_addr[15] = iface->lladdr[5]; +} + +static ssize_t ndL_send_icmp6(nd_iface_t *ifa, ndL_icmp6_msg_t *msg, size_t size, const uint8_t *hwaddr) +{ + assert(size >= sizeof(ndL_icmp6_msg_t)); + + msg->ip6_hdr.ip6_flow = htonl((6U << 28U) | (0U << 20U) | 0U); + msg->ip6_hdr.ip6_plen = htons(size - sizeof(struct ip6_hdr)); + msg->ip6_hdr.ip6_hops = 255; + msg->ip6_hdr.ip6_nxt = IPPROTO_ICMPV6; + + msg->icmp6_hdr.icmp6_cksum = ndL_calculate_icmp6_checksum(msg, size); + + struct sockaddr_ll addr; + memset(&addr, 0, sizeof(struct sockaddr_ll)); + addr.sll_family = AF_PACKET; + addr.sll_protocol = htons(ETH_P_IPV6); + addr.sll_ifindex = (int)ifa->index; + memcpy(addr.sll_addr, hwaddr, 6); + + return nd_sio_send(ifa->sio, (struct sockaddr *)&addr, sizeof(addr), msg, size); +} + +ssize_t nd_iface_write_na(nd_iface_t *iface, nd_addr_t *dst, uint8_t *dst_ll, nd_addr_t *tgt, bool router) +{ + struct + { + struct ip6_hdr ip; + struct nd_neighbor_advert na; + struct nd_opt_hdr opt; + uint8_t lladdr[6]; + } msg; + + memset(&msg, 0, sizeof(msg)); + + msg.ip.ip6_src = *tgt; + msg.ip.ip6_dst = *dst; + + msg.na.nd_na_type = ND_NEIGHBOR_ADVERT; + msg.na.nd_na_target = *tgt; + + if (nd_addr_is_multicast(dst)) + msg.na.nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED; + + if (router) + msg.na.nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; + + msg.opt.nd_opt_type = ND_OPT_TARGET_LINKADDR; + msg.opt.nd_opt_len = 1; + + memcpy(msg.lladdr, iface->lladdr, sizeof(msg.lladdr)); + + nd_log_info("Write NA tgt=%s, dst=%s [%x:%x:%x:%x:%x:%x dev %s]", nd_addr_to_string(tgt), nd_addr_to_string(dst), + dst_ll[0], dst_ll[1], dst_ll[2], dst_ll[3], dst_ll[4], dst_ll[5], iface->name); + + return ndL_send_icmp6(iface, (ndL_icmp6_msg_t *)&msg, sizeof(msg), dst_ll); +} + +ssize_t nd_iface_write_ns(nd_iface_t *ifa, nd_addr_t *tgt) +{ + struct + { + struct ip6_hdr ip; + struct nd_neighbor_solicit ns; + struct nd_opt_hdr opt; + uint8_t lladdr[6]; + } msg; + + memset(&msg, 0, sizeof(msg)); + + msg.ns.nd_ns_type = ND_NEIGHBOR_SOLICIT; + msg.ns.nd_ns_target = *tgt; + + msg.opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR; + msg.opt.nd_opt_len = 1; + + ndL_get_local_addr(ifa, &msg.ip.ip6_src); + + const uint8_t multicast[] = { 255, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 255, 0, 0, 0 }; + memcpy(&msg.ip.ip6_dst, multicast, sizeof(struct in6_addr)); + msg.ip.ip6_dst.s6_addr[13] = tgt->s6_addr[13]; + msg.ip.ip6_dst.s6_addr[14] = tgt->s6_addr[14]; + msg.ip.ip6_dst.s6_addr[15] = tgt->s6_addr[15]; + + memcpy(msg.lladdr, ifa->lladdr, sizeof(msg.lladdr)); + + uint8_t ll_mcast[6] = { 0x33, 0x33 }; + *(uint32_t *)&ll_mcast[2] = tgt->s6_addr32[3]; + + nd_log_trace("Write NS iface=%s, tgt=%s", ifa->name, nd_addr_to_string(tgt)); + + return ndL_send_icmp6(ifa, (ndL_icmp6_msg_t *)&msg, sizeof(msg), ll_mcast); +} + +bool nd_iface_set_allmulti(nd_iface_t *iface, bool on) +{ + nd_log_debug("%s all multicast mode for interface %s", on ? "Enabling" : "Disabling", iface->name); + + struct ifreq ifr; + memcpy(ifr.ifr_name, iface->name, IFNAMSIZ); + + if (ioctl(iface->sio->fd, SIOCGIFFLAGS, &ifr) < 0) + { + nd_log_error("Failed to get interface flags: %s", strerror(errno)); + return false; + } + + if (iface->old_allmulti < 0) + iface->old_allmulti = (ifr.ifr_flags & IFF_ALLMULTI) != 0; + + if (on == ((ifr.ifr_flags & IFF_ALLMULTI) != 0)) + return true; + + if (on) + ifr.ifr_flags |= IFF_ALLMULTI; + else + ifr.ifr_flags &= ~IFF_ALLMULTI; + + if (ioctl(iface->sio->fd, SIOCSIFFLAGS, &ifr) < 0) + { + nd_log_error("Failed to set interface flags: %s", strerror(errno)); + return false; + } + + return true; +} + +bool nd_iface_set_promisc(nd_iface_t *iface, bool on) +{ + nd_log_debug("%s promiscuous mode for interface %s", on ? "Enabling" : "Disabling", iface->name); + + struct ifreq ifr; + memcpy(ifr.ifr_name, iface->name, IFNAMSIZ); + + if (ioctl(iface->sio->fd, SIOCGIFFLAGS, &ifr) < 0) + { + nd_log_error("Failed to get interface flags: %s", strerror(errno)); + return false; + } + + if (iface->old_promisc < 0) + iface->old_promisc = (ifr.ifr_flags & IFF_PROMISC) != 0; + + if (on == ((ifr.ifr_flags & IFF_PROMISC) != 0)) + return true; + + if (on) + ifr.ifr_flags |= IFF_PROMISC; + else + ifr.ifr_flags &= ~IFF_PROMISC; + + if (ioctl(iface->sio->fd, SIOCSIFFLAGS, &ifr) < 0) + { + nd_log_error("Failed to set interface flags: %s", strerror(errno)); + return false; + } + + return true; +} + +void nd_iface_cleanup() +{ + ND_LL_FOREACH_S(ndL_first_iface, iface, tmp, next) + { + /* We're gonna be bad and just ignore refs here as all memory will soon be invalid anyway. */ + iface->refs = 1; + nd_iface_close(iface); + } +} diff --git a/src/iface.h b/src/iface.h new file mode 100644 index 0000000..cd2b493 --- /dev/null +++ b/src/iface.h @@ -0,0 +1,54 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_IFA_H +#define NDPPD_IFA_H + +#include "ndppd.h" +#include + +struct nd_iface +{ + nd_iface_t *next; + int refs; + + char name[IF_NAMESIZE]; + uint8_t lladdr[6]; + + unsigned int index; + + int old_allmulti; + int old_promisc; + + nd_proxy_t *proxy; + nd_neigh_t *neighs; /* All sessions expecting NA messages to arrive here. */ + nd_sio_t *sio; +}; + +extern bool nd_iface_no_restore_flags; + +nd_iface_t *nd_iface_open(const char *if_name, unsigned int if_index); +void nd_iface_close(nd_iface_t *iface); +ssize_t nd_iface_write_ns(nd_iface_t *ifa, nd_addr_t *tgt); +ssize_t nd_iface_write_na(nd_iface_t *iface, nd_addr_t *dst, uint8_t *dst_ll, nd_addr_t *tgt, bool router); +void nd_iface_get_local_addr(nd_iface_t *iface, nd_addr_t *addr); +bool nd_iface_set_allmulti(nd_iface_t *iface, bool on); +bool nd_iface_set_promisc(nd_iface_t *iface, bool on); +void nd_iface_cleanup(); + +#endif /* NDPPD_IFA_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..98bbd6b --- /dev/null +++ b/src/log.c @@ -0,0 +1,83 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "ndppd.h" + +bool ndL_syslog_opened; + +nd_loglevel_t nd_opt_verbosity = ND_LOG_TRACE; +bool nd_opt_syslog; + +static void ndL_open_syslog() +{ + if (ndL_syslog_opened) + return; + + setlogmask(LOG_UPTO(LOG_DEBUG)); + openlog("ndppd", LOG_CONS | LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_DAEMON); + ndL_syslog_opened = true; +} + +void nd_log_printf(nd_loglevel_t level, const char *fmt, ...) +{ + assert(level >= 0 && level <= ND_LOG_TRACE); + + if (level > nd_opt_verbosity) + return; + + if (nd_daemonized || nd_opt_syslog) + ndL_open_syslog(); + + char buf[512]; + + va_list va; + va_start(va, fmt); + + if (vsnprintf(buf, sizeof(buf), fmt, va) < 0) + abort(); + + va_end(va); + + if (ndL_syslog_opened) + { + const int pris[] = { LOG_ERR, LOG_INFO, LOG_DEBUG, LOG_DEBUG }; + syslog(pris[level], "%s", buf); + } + else + { + const char *names[] = { "error", "info", "debug", "trace" }; + + time_t time = nd_current_time / 1000; + + struct tm tm; + localtime_r(&time, &tm); + + char time_buf[32]; + strftime(time_buf, sizeof(time_buf), "%F %T", &tm); + + printf("%s.%03ld | %-8s | %s\n", time_buf, nd_current_time % 1000, names[level], buf); + } +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..9cf76c4 --- /dev/null +++ b/src/log.h @@ -0,0 +1,47 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_LOG_H +#define NDPPD_LOG_H + +#include + +typedef enum +{ + ND_LOG_ERROR, + ND_LOG_INFO, + ND_LOG_DEBUG, + ND_LOG_TRACE +} nd_loglevel_t; + +extern nd_loglevel_t nd_opt_verbosity; +extern bool nd_opt_syslog; + +void nd_log_printf(nd_loglevel_t level, const char *fmt, ...); + +#ifdef NDPPD_NO_TRACE +# define nd_log_trace(fmt, ...) (void) +#else +# define nd_log_trace(fmt, ...) nd_log_printf(ND_LOG_TRACE, fmt, ##__VA_ARGS__) +#endif + +#define nd_log_error(fmt, ...) nd_log_printf(ND_LOG_ERROR, fmt, ##__VA_ARGS__) +#define nd_log_info(fmt, ...) nd_log_printf(ND_LOG_INFO, fmt, ##__VA_ARGS__) +#define nd_log_debug(fmt, ...) nd_log_printf(ND_LOG_DEBUG, fmt, ##__VA_ARGS__) + +#endif /* NDPPD_LOG_H */ diff --git a/src/ndppd.c b/src/ndppd.c new file mode 100644 index 0000000..626e8a9 --- /dev/null +++ b/src/ndppd.c @@ -0,0 +1,236 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addr.h" +#include "conf.h" +#include "iface.h" +#include "ndppd.h" +#include "proxy.h" +#include "rtnl.h" +#include "rule.h" +#include "sio.h" + +#ifndef NDPPD_CONFIG_PATH +# define NDPPD_CONFIG_PATH "../ndppd.conf" +#endif + +long nd_current_time; +bool nd_daemonized; + +bool nd_opt_daemonize; +char *nd_opt_config_path; +char *nd_opt_pidfile_path; + +static bool ndL_check_pidfile() +{ + int fd = open(nd_opt_pidfile_path, O_RDWR); + + if (fd == -1) + { + if (errno == ENOENT) + return true; + + return false; + } + + bool result = flock(fd, LOCK_EX | LOCK_NB) == 0; + close(fd); + return result; +} + +static bool ndL_daemonize() +{ + int fd = open(nd_opt_pidfile_path, O_WRONLY | O_CREAT, 0644); + + if (fd == -1) + return false; + + if (flock(fd, LOCK_EX | LOCK_NB) < 0) + { + close(fd); + return false; + } + + pid_t pid = fork(); + + if (pid < 0) + { + // logger::error() << "Failed to fork during daemonize: " << logger::err(); + return false; + } + + if (pid > 0) + { + char buf[21]; + int len = snprintf(buf, sizeof(buf), "%d", pid); + + if (ftruncate(fd, 0) == -1) + nd_log_error("Failed to write PID file: ftruncate(): %s", strerror(errno)); + else if (write(fd, buf, len) != 0) + nd_log_error("Failed to write PID file: write(): %s", strerror(errno)); + + nd_iface_no_restore_flags = true; + exit(0); + } + + umask(0); + + pid_t sid = setsid(); + if (sid < 0) + { + // logger::error() << "Failed to setsid during daemonize: " << logger::err(); + return false; + } + + if (chdir("/") < 0) + { + // logger::error() << "Failed to change path during daemonize: " << logger::err(); + return false; + } + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + + return true; +} + +static void ndL_exit() +{ + nd_iface_cleanup(); + nd_rtnl_cleanup(); + nd_alloc_cleanup(); +} + +static void ndL_sig_exit(__attribute__((unused)) int sig) +{ + exit(0); +} + +int main(int argc, char *argv[]) +{ + atexit(ndL_exit); + signal(SIGINT, ndL_sig_exit); + signal(SIGTERM, ndL_sig_exit); + + static struct option long_options[] = { + { "config", 1, 0, 'c' }, { "daemon", 0, 0, 'd' }, { "verbose", 0, 0, 'v' }, + { "syslog", 0, 0, 1 }, { "pidfile", 1, 0, 'p' }, { NULL, 0, 0, 0 }, + }; + + for (int ch; (ch = getopt_long(argc, argv, "c:dp:v", long_options, NULL)) != -1;) + { + switch (ch) + { + case 'c': + nd_opt_config_path = nd_strdup(optarg); + break; + + case 'd': + nd_opt_daemonize = true; + break; + + case 'v': + if (nd_opt_verbosity < ND_LOG_ERROR) + nd_opt_verbosity++; + break; + + case 'p': + nd_opt_pidfile_path = nd_strdup(optarg); + break; + + case 1: + nd_opt_syslog = true; + break; + + default: + break; + } + } + + struct timeval t1; + gettimeofday(&t1, 0); + nd_current_time = t1.tv_sec * 1000 + t1.tv_usec / 1000; + + nd_log_info("ndppd " NDPPD_VERSION); + + if (nd_opt_pidfile_path && !ndL_check_pidfile()) + { + nd_log_error("Failed to lock pidfile. Is ndppd already running?"); + return -1; + } + + if (nd_opt_config_path == NULL) + nd_opt_config_path = NDPPD_CONFIG_PATH; + + nd_log_info("Loading configuration \"%s\"...", nd_opt_config_path); + + if (!nd_conf_load(nd_opt_config_path)) + { + nd_log_error("Failed to load configuration"); + return -1; + } + + if (!nd_proxy_startup()) + return -1; + + if (!nd_rtnl_open()) + return -1; + + if (nd_opt_daemonize && !ndL_daemonize()) + return -1; + + nd_rtnl_query_routes(); + + bool query_addresses = false; + + while (1) + { + if (nd_current_time >= nd_rtnl_dump_timeout) + nd_rtnl_dump_timeout = 0; + + if (!query_addresses && !nd_rtnl_dump_timeout) + { + query_addresses = true; + nd_rtnl_query_addresses(); + } + + if (!nd_sio_poll()) + { + break; + } + + gettimeofday(&t1, 0); + nd_current_time = t1.tv_sec * 1000 + t1.tv_usec / 1000; + } + + return 0; +} \ No newline at end of file diff --git a/src/ndppd.h b/src/ndppd.h new file mode 100644 index 0000000..8385f2a --- /dev/null +++ b/src/ndppd.h @@ -0,0 +1,92 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_H +#define NDPPD_H + +#include +#include +#include +#include + +#define NDPPD_VERSION "1.0-beta1" + +typedef struct nd_iface nd_iface_t; +typedef struct nd_sio nd_sio_t; +typedef struct nd_proxy nd_proxy_t; +typedef struct nd_conf nd_conf_t; +typedef struct in6_addr nd_addr_t; +typedef struct nd_conf_rule nd_conf_rule_t; +typedef struct nd_conf_proxy nd_conf_proxy_t; +typedef struct nd_rule nd_rule_t; +typedef struct nd_neigh nd_neigh_t; + +extern long nd_current_time; +extern bool nd_daemonized; +extern bool nd_opt_syslog; +extern bool nd_opt_daemonize; + +#define ND_LL_PREPEND(head, el, next) \ + do \ + { \ + (el)->next = (head); \ + (head) = (el); \ + } while (0) + +#define ND_LL_DELETE(head, el, next) \ + do \ + { \ + __typeof(el) _last = (head); \ + while (_last != NULL && _last->next != (el)) \ + _last = _last->next; \ + if (_last) \ + _last->next = (el)->next; \ + if ((head) == (el)) \ + (head) = (el)->next; \ + } while (0) + +#define ND_LL_COUNT(head, count, next) \ + do \ + { \ + (count) = 0; \ + for (__typeof(head) _el = (head); _el; _el = _el->next) \ + (count)++; \ + } while (0) + +#define ND_LL_FOREACH(head, el, next) for (__typeof(head)(el) = (head); (el); (el) = (el)->next) + +#define ND_LL_FOREACH_S(head, el, tmp, next) \ + for (__typeof(head)(el) = (head), (tmp) = (head) ? (head)->next : NULL; (el); \ + (el) = (tmp), (tmp) = (el) ? (el)->next : NULL) + +#define ND_LL_FOREACH_S_NODEF(head, el, tmp, next) \ + for ((el) = (head), (tmp) = (head) ? (head)->next : NULL; (el); (el) = (tmp), (tmp) = (el) ? (el)->next : NULL) + +#define ND_LL_FOREACH_NODEF(head, el, next) for ((el) = (head); (el); (el) = (el)->next) + +#define ND_LL_SEARCH(head, el, next, pred) \ + do \ + { \ + for (el = (head); el && !(pred); el = el->next) \ + ; \ + } while (0) + +#include "alloc.h" +#include "log.h" + +#endif /* NDPPD_H */ diff --git a/src/neigh.c b/src/neigh.c new file mode 100644 index 0000000..84957e2 --- /dev/null +++ b/src/neigh.c @@ -0,0 +1,45 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include + +#include "neigh.h" +#include "ndppd.h" + +static nd_neigh_t *ndL_free_neighs; + +nd_neigh_t *nd_alloc_neigh() +{ + nd_neigh_t *neigh = ndL_free_neighs; + + if (neigh) + ND_LL_DELETE(ndL_free_neighs, neigh, next_in_proxy); + else + neigh = ND_ALLOC(nd_neigh_t); + + memset(neigh, 0, sizeof(nd_neigh_t)); + + return neigh; +} + +void nd_free_neigh(nd_neigh_t *session) +{ + ND_LL_PREPEND(ndL_free_neighs, session, next_in_proxy); +} + + diff --git a/src/neigh.h b/src/neigh.h new file mode 100644 index 0000000..c5e5f13 --- /dev/null +++ b/src/neigh.h @@ -0,0 +1,65 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_NEIGH_H +#define NDPPD_NEIGH_H + +#include "ndppd.h" + +typedef enum +{ + /* + * Address resolution is in progress. + */ + ND_STATE_INCOMPLETE, + + /* + * + */ + ND_STATE_VALID, + + /* + * Resolution was successful, but this entry is getting old. + */ + ND_STATE_VALID_REFRESH, + + /* + * Resolution failed, and further Neighbor Solicitation messages will be ignored until + * the session is removed or a Neighbor Advertisement is received. + */ + ND_STATE_INVALID, + +} nd_state_t; + +struct nd_neigh +{ + nd_neigh_t *next_in_proxy; + nd_neigh_t *next_in_iface; + nd_addr_t tgt; + int attempt; + long touched_at; + long used_at; + nd_state_t state; + nd_iface_t *iface; +}; + +nd_neigh_t *nd_alloc_neigh(); +void nd_free_neigh(nd_neigh_t *session); +void nd_session_send_ns(nd_neigh_t *session); + +#endif /* NDPPD_NEIGH_H */ diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 0000000..b6aebdb --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,222 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ + +#include + +#include "addr.h" +#include "iface.h" +#include "ndppd.h" +#include "neigh.h" +#include "proxy.h" +#include "rtnl.h" +#include "rule.h" + +static nd_proxy_t *ndL_proxies; + +extern int nd_conf_invalid_ttl; +extern int nd_conf_valid_ttl; +extern int nd_conf_renew; +extern int nd_conf_retrans_limit; +extern int nd_conf_retrans_time; +extern bool nd_conf_keepalive; + +nd_proxy_t *nd_proxy_create(const char *ifname) +{ + nd_proxy_t *proxy; + + ND_LL_SEARCH(ndL_proxies, proxy, next, !strcmp(proxy->ifname, ifname)); + + if (proxy) + { + nd_log_error("Proxy already exists for interface \"%s\"", ifname); + return NULL; + } + + proxy = ND_ALLOC(nd_proxy_t); + + ND_LL_PREPEND(ndL_proxies, proxy, next); + + proxy->iface = NULL; + proxy->rules = NULL; + proxy->neighs = NULL; + proxy->router = false; + + strcpy(proxy->ifname, ifname); + + return proxy; +} + +void nd_proxy_handle_ns(nd_proxy_t *proxy, nd_addr_t *src, __attribute__((unused)) nd_addr_t *dst, nd_addr_t *tgt, + uint8_t *src_ll) +{ + nd_log_trace("Handle NA src=%s [%x:%x:%x:%x:%x:%x], dst=%s, tgt=%s", nd_addr_to_string(src), src_ll[0], src_ll[1], + src_ll[2], src_ll[3], src_ll[4], src_ll[5], nd_addr_to_string(dst), nd_addr_to_string(tgt)); + + nd_neigh_t *neigh; + + ND_LL_FOREACH_NODEF(proxy->neighs, neigh, next_in_proxy) + { + if (!nd_addr_eq(&neigh->tgt, tgt)) + continue; + + if (neigh->state == ND_STATE_VALID || neigh->state == ND_STATE_VALID_REFRESH) + { + neigh->used_at = nd_current_time; + nd_iface_write_na(proxy->iface, src, src_ll, tgt, proxy->router); + return; + } + + return; + } + + /* If we get down here it means we don't have any valid sessions we can use. + * See if we can find one more more matching rules. */ + + nd_rule_t *rule; + ND_LL_SEARCH(proxy->rules, rule, next, nd_addr_match(&rule->addr, tgt, rule->prefix)); + + if (!rule) + return; + + neigh = nd_alloc_neigh(); + neigh->touched_at = nd_current_time; + neigh->tgt = *tgt; + + ND_LL_PREPEND(proxy->neighs, neigh, next_in_proxy); + + if (rule->is_auto) + { + /* TODO: Loop through valid routes. */ + + nd_rtnl_route_t *route = nd_rtnl_find_route(tgt, 254); + + if (!route || route->oif == proxy->iface->index) + { + /* Could not find a matching route. */ + neigh->state = ND_STATE_INVALID; + return; + } + + if (!(neigh->iface = nd_iface_open(NULL, route->oif))) + { + /* Could not open interface. */ + neigh->state = ND_STATE_INVALID; + return; + } + } + else if ((neigh->iface = rule->iface)) + { + neigh->iface->refs++; + } + + if (neigh->iface) + { + neigh->state = ND_STATE_INCOMPLETE; + nd_iface_write_ns(neigh->iface, tgt); + + ND_LL_PREPEND(neigh->iface->neighs, neigh, next_in_iface); + } + else + { + neigh->state = ND_STATE_VALID; + nd_iface_write_na(proxy->iface, src, src_ll, tgt, proxy->router); + } +} + +void nd_proxy_update_neighs(nd_proxy_t *proxy) +{ + ND_LL_FOREACH_S(proxy->neighs, neigh, tmp, next_in_proxy) + { + switch (neigh->state) + { + case ND_STATE_INCOMPLETE: + if ((nd_current_time - neigh->touched_at) < nd_conf_retrans_time) + break; + + neigh->touched_at = nd_current_time; + + if (++neigh->attempt > 3) + { + neigh->state = ND_STATE_INVALID; + break; + } + + nd_iface_write_ns(neigh->iface, &neigh->tgt); + break; + + case ND_STATE_INVALID: + if ((nd_current_time - neigh->touched_at) < nd_conf_invalid_ttl) + break; + + ND_LL_DELETE(neigh->iface->neighs, neigh, next_in_iface); + ND_LL_DELETE(proxy->neighs, neigh, next_in_proxy); + + nd_iface_close(neigh->iface); + nd_free_neigh(neigh); + break; + + case ND_STATE_VALID: + if (nd_current_time - neigh->touched_at < nd_conf_valid_ttl - nd_conf_renew) + break; + + + + + + /* TODO: Send solicit. */ + break; + + case ND_STATE_VALID_REFRESH: + if ((nd_current_time - neigh->touched_at) < nd_conf_retrans_time) + break; + + if (++neigh->attempt > 3) + { + neigh->state = ND_STATE_INVALID; + neigh->touched_at = nd_current_time; + break; + } + + /* TODO: Send solicit. */ + break; + } + } +} + +bool nd_proxy_startup() +{ + ND_LL_FOREACH(ndL_proxies, proxy, next) + { + if (!(proxy->iface = nd_iface_open(proxy->ifname, 0))) + return false; + + if (proxy->promisc) + nd_iface_set_promisc(proxy->iface, true); + else + nd_iface_set_allmulti(proxy->iface, true); + + ND_LL_FOREACH(proxy->rules, rule, next) + { + if (rule->ifname[0] && !(rule->iface = nd_iface_open(rule->ifname, 0))) + return false; + } + } + + return true; +} diff --git a/src/proxy.h b/src/proxy.h new file mode 100644 index 0000000..4f5649b --- /dev/null +++ b/src/proxy.h @@ -0,0 +1,44 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_PROXY_H +#define NDPPD_PROXY_H + +#include + +#include "ndppd.h" + +struct nd_proxy +{ + nd_proxy_t *next; + char ifname[IF_NAMESIZE]; + + nd_iface_t *iface; + nd_rule_t *rules; + nd_neigh_t *neighs; + bool router; + bool promisc; +}; + +/* proxy.c */ +nd_proxy_t *nd_proxy_create(const char *ifname); +void nd_proxy_handle_na(nd_proxy_t *proxy, nd_addr_t *src, nd_addr_t *tgt); +void nd_proxy_handle_ns(nd_proxy_t *proxy, nd_addr_t *src, nd_addr_t *dst, nd_addr_t *tgt, uint8_t *src_ll); +bool nd_proxy_startup(); + +#endif // NDPPD_PROXY_H diff --git a/src/rtnl.c b/src/rtnl.c new file mode 100644 index 0000000..8682c12 --- /dev/null +++ b/src/rtnl.c @@ -0,0 +1,310 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include + +#include "addr.h" +#include "ndppd.h" +#include "rtnl.h" +#include "sio.h" + +static nd_sio_t *ndL_sio; +static nd_rtnl_route_t *ndL_routes, *ndL_free_routes; +static nd_rtnl_addr_t *ndL_addrs, *ndL_free_addrs; + +long nd_rtnl_dump_timeout; + +/* + * This will ensure the linked list is kept sorted, so it will be easier to find a match. + */ +static void ndL_insert_route(nd_rtnl_route_t *route) +{ + nd_rtnl_route_t *prev = NULL; + + ND_LL_FOREACH(ndL_routes, cur, next) + { + if (route->pflen >= cur->pflen && route->metrics <= cur->metrics) + break; + + prev = cur; + } + + if (prev) + { + route->next = prev->next; + prev->next = route; + } + else + { + ND_LL_PREPEND(ndL_routes, route, next); + } +} + +static void ndL_handle_newaddress(struct ifaddrmsg *msg, int length) +{ + nd_addr_t *addr = NULL; + + for (struct rtattr *rta = IFA_RTA(msg); RTA_OK(rta, length); rta = RTA_NEXT(rta, length)) + { + if (rta->rta_type == IFA_ADDRESS) + addr = (nd_addr_t *)RTA_DATA(rta); + } + + if (!addr) + return; + + nd_rtnl_addr_t *rt_addr; + + ND_LL_FOREACH_NODEF(ndL_addrs, rt_addr, next) + { + if (nd_addr_eq(&rt_addr->addr, addr) && rt_addr->pflen == msg->ifa_prefixlen) + return; + } + + if ((rt_addr = ndL_free_addrs)) + ND_LL_DELETE(ndL_free_addrs, rt_addr, next); + else + rt_addr = ND_ALLOC(nd_rtnl_addr_t); + + ND_LL_PREPEND(ndL_addrs, rt_addr, next); + + rt_addr->pflen = msg->ifa_prefixlen; + rt_addr->iif = msg->ifa_index; + rt_addr->addr = *addr; + + nd_log_debug("rtnl: NEWADDR %s/%d if %d", nd_addr_to_string(addr), msg->ifa_prefixlen, msg->ifa_index); +} + +static void ndL_handle_delroute(struct rtmsg *msg, int rtl) +{ + nd_addr_t *dst = NULL; + int oif = 0; + + for (struct rtattr *rta = RTM_RTA(msg); RTA_OK(rta, rtl); rta = RTA_NEXT(rta, rtl)) + { + if (rta->rta_type == RTA_OIF) + oif = *(int *)RTA_DATA(rta); + else if (rta->rta_type == RTA_DST) + dst = (nd_addr_t *)RTA_DATA(rta); + } + + if (!dst || !oif) + return; + + ND_LL_FOREACH(ndL_routes, route, next) + { + if (nd_addr_eq(&route->addr, dst) && route->pflen == msg->rtm_dst_len && route->table == msg->rtm_table) + { + nd_log_debug("rtnl: DELROUTE %s/%d dev %d table %d", nd_addr_to_string(dst), msg->rtm_dst_len, oif, + msg->rtm_table); + ND_LL_DELETE(ndL_routes, route, next); + ND_LL_PREPEND(ndL_free_routes, route, next); + return; + } + } +} + +static void ndL_handle_newroute(struct rtmsg *msg, int rtl) +{ + nd_addr_t *dst = NULL; + int oif = 0; + int metrics = 0; + + for (struct rtattr *rta = RTM_RTA(msg); RTA_OK(rta, rtl); rta = RTA_NEXT(rta, rtl)) + { + if (rta->rta_type == RTA_OIF) + oif = *(int *)RTA_DATA(rta); + else if (rta->rta_type == RTA_DST) + dst = (nd_addr_t *)RTA_DATA(rta); + else if (rta->rta_type == RTA_METRICS) + metrics = *(int *)RTA_DATA(rta); + } + + if (!dst || !oif) + return; + + nd_rtnl_route_t *route; + + ND_LL_FOREACH_NODEF(ndL_routes, route, next) + { + if (nd_addr_eq(&route->addr, dst) && route->pflen == msg->rtm_dst_len && route->table == msg->rtm_table) + return; + } + + if ((route = ndL_free_routes)) + ND_LL_DELETE(ndL_free_routes, route, next); + else + route = ND_ALLOC(nd_rtnl_route_t); + + route->addr = *dst; + route->pflen = msg->rtm_dst_len; + route->oif = oif; + route->table = msg->rtm_table; + route->metrics = metrics; + + ndL_insert_route(route); + + nd_log_debug("rtnl: NEWROUTE %s/%d dev %d table %d", nd_addr_to_string(dst), msg->rtm_dst_len, oif, msg->rtm_table); +} + +static void ndL_sio_handler(__attribute__((unused)) nd_sio_t *unused1, __attribute__((unused)) int unused2) +{ + uint8_t buf[4096]; + + for (;;) + { + ssize_t len = nd_sio_recv(ndL_sio, NULL, 0, buf, sizeof(buf)); + + if (len < 0) + /* Failed. */ + return; + + for (struct nlmsghdr *hdr = (struct nlmsghdr *)buf; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) + { + if (hdr->nlmsg_type == NLMSG_DONE) + { + nd_rtnl_dump_timeout = 0; + break; + } + + if (hdr->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *e = (struct nlmsgerr *)NLMSG_DATA(hdr); + nd_log_error("rtnl: Error \"%s\", type=%d", strerror(-e->error), e->msg.nlmsg_type); + continue; + } + + if (hdr->nlmsg_type == RTM_NEWROUTE) + ndL_handle_newroute((struct rtmsg *)NLMSG_DATA(hdr), RTM_PAYLOAD(hdr)); + else if (hdr->nlmsg_type == RTM_DELROUTE) + ndL_handle_delroute((struct rtmsg *)NLMSG_DATA(hdr), RTM_PAYLOAD(hdr)); + else if (hdr->nlmsg_type == RTM_NEWADDR) + ndL_handle_newaddress((struct ifaddrmsg *)NLMSG_DATA(hdr), IFA_PAYLOAD(hdr)); + } + } +} + +bool nd_rtnl_open() +{ + if (ndL_sio != NULL) + return true; + + if (!(ndL_sio = nd_sio_open(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE))) + { + nd_log_error("Failed to open netlink socket: %s", strerror(errno)); + return false; + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = (1 << (RTNLGRP_IPV6_IFADDR - 1)) | (1 << (RTNLGRP_IPV6_ROUTE - 1)); + + if (!nd_sio_bind(ndL_sio, (struct sockaddr *)&addr, sizeof(addr))) + { + nd_log_error("Failed to bind netlink socket: %s", strerror(errno)); + nd_sio_close(ndL_sio); + ndL_sio = NULL; + return false; + } + + ndL_sio->handler = ndL_sio_handler; + + return true; +} + +void nd_rtnl_cleanup() +{ + if (ndL_sio) + nd_sio_close(ndL_sio); +} + +bool nd_rtnl_query_routes() +{ + if (nd_rtnl_dump_timeout) + return false; + + struct + { + struct nlmsghdr hdr; + struct rtmsg msg; + } req; + + memset(&req, 0, sizeof(req)); + + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.hdr.nlmsg_type = RTM_GETROUTE; + + req.msg.rtm_protocol = RTPROT_UNSPEC; + req.msg.rtm_table = RT_TABLE_UNSPEC; + req.msg.rtm_family = AF_INET6; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + nd_rtnl_dump_timeout = nd_current_time + 5000; + + nd_sio_send(ndL_sio, (struct sockaddr *)&addr, sizeof(addr), &req, sizeof(req)); + return false; +} + +bool nd_rtnl_query_addresses() +{ + if (nd_rtnl_dump_timeout) + return false; + + struct + { + struct nlmsghdr hdr; + struct ifaddrmsg msg; + } req; + + memset(&req, 0, sizeof(req)); + + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.hdr.nlmsg_type = RTM_GETADDR; + req.hdr.nlmsg_seq = 1; + + req.msg.ifa_family = AF_INET6; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + nd_rtnl_dump_timeout = nd_current_time + 5000; + + nd_sio_send(ndL_sio, (struct sockaddr *)&addr, sizeof(addr), &req, sizeof(req)); + return false; +} + +nd_rtnl_route_t *nd_rtnl_find_route(nd_addr_t *addr, int table) +{ + ND_LL_FOREACH(ndL_routes, route, next) + { + if (nd_addr_match(&route->addr, addr, route->pflen) && route->table == table) + return route; + } + + return NULL; +} \ No newline at end of file diff --git a/src/rtnl.h b/src/rtnl.h new file mode 100644 index 0000000..e9e884a --- /dev/null +++ b/src/rtnl.h @@ -0,0 +1,54 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_RTNL_H +#define NDPPD_RTNL_H + +#include "ndppd.h" + +typedef struct nd_rtnl_route nd_rtnl_route_t; +typedef struct nd_rtnl_addr nd_rtnl_addr_t; + +struct nd_rtnl_route +{ + nd_rtnl_route_t *next; + nd_addr_t addr; + unsigned int oif; + int pflen; + int table; + int metrics; +}; + +struct nd_rtnl_addr +{ + nd_rtnl_addr_t *next; + int iif; + nd_addr_t addr; + int pflen; +}; + +extern long nd_rtnl_dump_timeout; + + +bool nd_rtnl_open(); +void nd_rtnl_cleanup(); +bool nd_rtnl_query_addresses(); +bool nd_rtnl_query_routes(); +nd_rtnl_route_t *nd_rtnl_find_route(nd_addr_t *addr, int table); + +#endif /* NDPPD_RTNL_H */ diff --git a/src/rule.c b/src/rule.c new file mode 100644 index 0000000..385f5cd --- /dev/null +++ b/src/rule.c @@ -0,0 +1,28 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include "ndppd.h" +#include "proxy.h" +#include "rule.h" + +nd_rule_t *nd_rule_create(nd_proxy_t *proxy) +{ + nd_rule_t *rule = ND_ALLOC(nd_rule_t); + ND_LL_PREPEND(proxy->rules, rule, next); + return rule; +} diff --git a/src/rule.h b/src/rule.h new file mode 100644 index 0000000..13ffd08 --- /dev/null +++ b/src/rule.h @@ -0,0 +1,40 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_RULE_H +#define NDPPD_RULE_H + +#include "ndppd.h" + +struct nd_rule +{ + nd_rule_t *next; + + char ifname[IF_NAMESIZE]; + + nd_addr_t addr; + int prefix; + + nd_iface_t *iface; + bool is_auto; +}; + +/* rule.c */ +nd_rule_t *nd_rule_create(nd_proxy_t *proxy); + +#endif // NDPPD_RULE_H diff --git a/src/sio.c b/src/sio.c new file mode 100644 index 0000000..d794de1 --- /dev/null +++ b/src/sio.c @@ -0,0 +1,271 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#include +#include +#include +#include +#include +#include + +#ifndef NDPPD_NO_USE_EPOLL +# include +#else +# include +# include +# ifdef EPOLLIN +# undef EPOLLIN +# endif +# define EPOLLIN POLLIN +#endif + +#include "ndppd.h" +#include "sio.h" + +static nd_sio_t *ndL_first_sio, *ndL_first_free_sio; + +#ifndef NDPPD_NO_USE_EPOLL +static int ndL_epoll_fd; +#else +# ifndef NDPPD_STATIC_POLLFDS_SIZE +# define NDPPD_STATIC_POLLFDS_SIZE 32 +# endif + +static struct pollfd static_pollfds[NDPPD_STATIC_POLLFDS_SIZE]; +static struct pollfd *pollfds = static_pollfds; +static int pollfds_size = NDPPD_STATIC_POLLFDS_SIZE; +static int pollfds_count = 0; +static bool ndL_dirty; + +static void ndL_refresh_pollfds() +{ + int count; + + ND_LL_COUNT(ndL_first_sio, count, next); + + if (count > pollfds_size) + { + int new_pollfds_size = count * 2; + + pollfds = (struct pollfd *)realloc(pollfds == static_pollfds ? NULL : pollfds, + new_pollfds_size * sizeof(struct pollfd)); + + /* TODO: Validate return value */ + + pollfds_size = new_pollfds_size; + } + + int index = 0; + + ND_LL_FOREACH(ndL_first_sio, sio, next) + { + pollfds[index].fd = sio->fd; + pollfds[index].revents = 0; + pollfds[index].events = POLLIN; + index++; + } + + pollfds_count = index; +} +#endif + +nd_sio_t *nd_sio_open(int domain, int type, int protocol) +{ + int fd = socket(domain, type, protocol); + + if (fd < 0) + return NULL; + + /* Non-blocking. */ + + int on = 1; + if (ioctl(fd, FIONBIO, (char *)&on) < 0) + { + close(fd); + return NULL; + } + + /* Allocate the nd_sio_t object. */ + + nd_sio_t *sio = ndL_first_free_sio; + + if (sio) + ND_LL_DELETE(ndL_first_free_sio, sio, next); + else + sio = ND_ALLOC(nd_sio_t); + + ND_LL_PREPEND(ndL_first_sio, sio, next); + + sio->fd = fd; + +#ifndef NDPPD_NO_USE_EPOLL + if (ndL_epoll_fd <= 0 && (ndL_epoll_fd = epoll_create(1)) < 0) + { + nd_log_error("epoll_create() failed: %s", strerror(errno)); + nd_sio_close(sio); + return NULL; + } + + struct epoll_event event = { .events = EPOLLIN, .data.ptr = sio }; + + if (epoll_ctl(ndL_epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0) + { + nd_log_error("epoll_ctl() failed: %s", strerror(errno)); + nd_sio_close(sio); + return NULL; + } + +#else + /* Make sure our pollfd array is updated. */ + ndL_dirty = true; +#endif + + return sio; +} + +void nd_sio_close(nd_sio_t *sio) +{ + close(sio->fd); + +#ifdef NDPPD_NO_USE_EPOLL + ndL_dirty = true; +#endif + + ND_LL_DELETE(ndL_first_sio, sio, next); + ND_LL_PREPEND(ndL_first_free_sio, sio, next); +} + +ssize_t nd_sio_send(nd_sio_t *sio, const struct sockaddr *addr, size_t addrlen, const void *msg, size_t msglen) +{ + struct iovec iov; + iov.iov_len = msglen; + iov.iov_base = (caddr_t)msg; + + struct msghdr mhdr; + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.msg_name = (caddr_t)addr, mhdr.msg_namelen = addrlen; + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + + ssize_t len; + + if ((len = sendmsg(sio->fd, &mhdr, 0)) < 0) + { + printf("send err %s\n", strerror(errno)); + return -1; + } + + return len; +} + +ssize_t nd_sio_recv(nd_sio_t *sio, struct sockaddr *addr, size_t addrlen, void *msg, size_t msglen) +{ + struct iovec iov; + iov.iov_len = msglen; + iov.iov_base = (caddr_t)msg; + + struct msghdr mhdr; + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.msg_name = (caddr_t)addr; + mhdr.msg_namelen = addrlen; + mhdr.msg_iov = &iov; + mhdr.msg_iovlen = 1; + + int len; + + if ((len = recvmsg(sio->fd, &mhdr, 0)) < 0) + return -1; + + return len; +} + +bool nd_sio_bind(nd_sio_t *sio, const struct sockaddr *addr, size_t addrlen) +{ + return bind(sio->fd, addr, addrlen) == 0; +} + +bool nd_sio_poll() +{ +#ifndef NDPPD_NO_USE_EPOLL + struct epoll_event events[8]; + + int count = epoll_wait(ndL_epoll_fd, events, 8, 250); + + if (count < 0) + { + nd_log_error("epoll() failed: %s", strerror(errno)); + return false; + } + + for (int i = 0; i < count; i++) + { + nd_sio_t *sio = (nd_sio_t *)events[i].data.ptr; + + if (sio->handler) + sio->handler(sio, events[i].events); + } + +#else + if (ndL_dirty) + { + ndL_refresh_pollfds(); + ndL_dirty = false; + } + + int len = poll(pollfds, pollfds_count, 250); + + if (len < 0) + return false; + + if (len == 0) + return true; + + for (int i = 0; i < pollfds_count; i++) + { + if (pollfds[i].revents == 0) + continue; + + for (nd_sio_t *sio = ndL_first_sio; sio; sio = sio->next) + { + if (sio->fd == pollfds[i].fd) + { + if (sio->handler != NULL) + sio->handler(sio, pollfds[i].revents); + + break; + } + } + } +#endif + + return true; +} + +void nd_sio_cleanup() +{ + ND_LL_FOREACH_S(ndL_first_sio, sio, tmp, next) + { + nd_sio_close(sio); + } + + if (ndL_epoll_fd > 0) + { + close(ndL_epoll_fd); + ndL_epoll_fd = 0; + } +} diff --git a/src/sio.h b/src/sio.h new file mode 100644 index 0000000..d242f62 --- /dev/null +++ b/src/sio.h @@ -0,0 +1,43 @@ +/* + * This file is part of ndppd. + * + * Copyright (C) 2011-2019 Daniel Adolfsson + * + * ndppd 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 3 of the License, or + * (at your option) any later version. + * + * ndppd 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 ndppd. If not, see . + */ +#ifndef NDPPD_SIO_H +#define NDPPD_SIO_H + +#include "ndppd.h" + +typedef void(nd_sio_handler_t)(nd_sio_t *sio, int events); + +struct nd_sio +{ + nd_sio_t *next; + int fd; + uintptr_t data; + nd_sio_handler_t *handler; +}; + +/* sio.c */ +nd_sio_t *nd_sio_open(int domain, int type, int protocol); +void nd_sio_close(nd_sio_t *nio); +bool nd_sio_bind(nd_sio_t *sio, const struct sockaddr *addr, size_t addrlen); +ssize_t nd_sio_send(nd_sio_t *sio, const struct sockaddr *addr, size_t addrlen, const void *msg, size_t msglen); +ssize_t nd_sio_recv(nd_sio_t *sio, struct sockaddr *addr, size_t addrlen, void *msg, size_t msglen); +bool nd_sio_poll(); +void nd_sio_cleanup(); + +#endif /* NDPPD_SIO_H */