Initial commit
This commit is contained in:
commit
141f39f20a
6
.clang-format
Normal file
6
.clang-format
Normal file
@ -0,0 +1,6 @@
|
||||
BreakBeforeBraces: Allman
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 120
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
Cpp11BracedListStyle: false
|
||||
IndentPPDirectives: AfterHash
|
70
.clang-tidy
Normal file
70
.clang-tidy
Normal file
@ -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
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.idea
|
||||
cmake-build-*
|
||||
ndppd.conf
|
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
@ -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)
|
||||
|
103
ChangeLog
Normal file
103
ChangeLog
Normal file
@ -0,0 +1,103 @@
|
||||
2019-12-xx Daniel Adolfsson <daniel@ashen.se>
|
||||
|
||||
* 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 <johnathan.sharratt@gmail.com>
|
||||
|
||||
* 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 <daniel@priv.nu>
|
||||
|
||||
* 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 <daniel@priv.nu>
|
||||
|
||||
* 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 <daniel@priv.nu>
|
||||
|
||||
* Version 0.2.3
|
||||
|
||||
2012-02-06 Daniel Adolfsson <daniel@priv.nu>
|
||||
|
||||
* 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 <daniel@priv.nu>
|
||||
|
||||
* Author changed e-mail address; updated copyright info.
|
||||
|
||||
2011-10-11 Daniel Adolfsson <daniel.adolfsson@tuhox.com>
|
||||
|
||||
* Initial Release; 0.2.1
|
35
Makefile
Normal file
35
Makefile
Normal file
@ -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}
|
31
README.md
Normal file
31
README.md
Normal file
@ -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
|
36
ndppd.8.adoc
Normal file
36
ndppd.8.adoc
Normal file
@ -0,0 +1,36 @@
|
||||
= ndppd(8)
|
||||
Daniel Adolfsson <daniel@ashen.se>
|
||||
: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.
|
||||
|
30
ndppd.conf-dist
Normal file
30
ndppd.conf-dist
Normal file
@ -0,0 +1,30 @@
|
||||
# Please refer to man ndppd.conf(8) for details.
|
||||
|
||||
# invalid-ttl <milliseconds>
|
||||
#invalid-ttl 5000
|
||||
|
||||
# valid-ttl <milliseconds>
|
||||
#valid-ttl 30000
|
||||
|
||||
# renew <milliseconds>
|
||||
#renew 5000
|
||||
|
||||
# retransmit-limit <count>
|
||||
#retransmit-limit 3
|
||||
|
||||
# retransmit-time <milliseconds>
|
||||
#retransmit-time 1000
|
||||
|
||||
# keepalive <yes|no>
|
||||
#keepalive yes
|
||||
|
||||
# proxy <interface>
|
||||
proxy eth0 {
|
||||
# router <yes|no>
|
||||
#router yes
|
||||
|
||||
# rule <ip>[/<prefix>]
|
||||
rule dead::beef:: {
|
||||
#auto
|
||||
}
|
||||
}
|
107
ndppd.conf.5.adoc
Normal file
107
ndppd.conf.5.adoc
Normal file
@ -0,0 +1,107 @@
|
||||
= ndppd.conf(5)
|
||||
Daniel Adolfsson <daniel@ashen.se>
|
||||
: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.
|
11
ndppd.service
Normal file
11
ndppd.service
Normal file
@ -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
|
108
src/addr.c
Normal file
108
src/addr.c
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <arpa/inet.h>
|
||||
#include <string.h>
|
||||
|
||||
#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];
|
||||
}
|
31
src/addr.h
Normal file
31
src/addr.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 */
|
92
src/alloc.c
Normal file
92
src/alloc.c
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
28
src/alloc.h
Normal file
28
src/alloc.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 */
|
563
src/conf.c
Normal file
563
src/conf.c
Normal file
@ -0,0 +1,563 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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;
|
||||
}
|
27
src/conf.h
Normal file
27
src/conf.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NDPPD_CF_H
|
||||
#define NDPPD_CF_H
|
||||
|
||||
#include "ndppd.h"
|
||||
|
||||
/* cf.c */
|
||||
bool nd_conf_load(const char *path);
|
||||
|
||||
#endif // NDPPD_CF_H
|
522
src/iface.c
Normal file
522
src/iface.c
Normal file
@ -0,0 +1,522 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
54
src/iface.h
Normal file
54
src/iface.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NDPPD_IFA_H
|
||||
#define NDPPD_IFA_H
|
||||
|
||||
#include "ndppd.h"
|
||||
#include <net/if.h>
|
||||
|
||||
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 */
|
83
src/log.c
Normal file
83
src/log.c
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <syslog.h>
|
||||
#include <time.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
47
src/log.h
Normal file
47
src/log.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NDPPD_LOG_H
|
||||
#define NDPPD_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
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 */
|
236
src/ndppd.c
Normal file
236
src/ndppd.c
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
92
src/ndppd.h
Normal file
92
src/ndppd.h
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NDPPD_H
|
||||
#define NDPPD_H
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
45
src/neigh.c
Normal file
45
src/neigh.c
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
|
65
src/neigh.h
Normal file
65
src/neigh.h
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 */
|
222
src/proxy.c
Normal file
222
src/proxy.c
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
44
src/proxy.h
Normal file
44
src/proxy.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef NDPPD_PROXY_H
|
||||
#define NDPPD_PROXY_H
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
#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
|
310
src/rtnl.c
Normal file
310
src/rtnl.c
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#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;
|
||||
}
|
54
src/rtnl.h
Normal file
54
src/rtnl.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 */
|
28
src/rule.c
Normal file
28
src/rule.c
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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;
|
||||
}
|
40
src/rule.h
Normal file
40
src/rule.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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
|
271
src/sio.c
Normal file
271
src/sio.c
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef NDPPD_NO_USE_EPOLL
|
||||
# include <sys/epoll.h>
|
||||
#else
|
||||
# include <poll.h>
|
||||
# include <stdlib.h>
|
||||
# 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;
|
||||
}
|
||||
}
|
43
src/sio.h
Normal file
43
src/sio.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of ndppd.
|
||||
*
|
||||
* Copyright (C) 2011-2019 Daniel Adolfsson <daniel@ashen.se>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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 */
|
Loading…
x
Reference in New Issue
Block a user