Initial commit

This commit is contained in:
Daniel Adolfsson 2019-12-11 11:34:34 +01:00
commit 141f39f20a
33 changed files with 3446 additions and 0 deletions

6
.clang-format Normal file
View File

@ -0,0 +1,6 @@
BreakBeforeBraces: Allman
IndentWidth: 4
ColumnLimit: 120
AllowShortFunctionsOnASingleLine: None
Cpp11BracedListStyle: false
IndentPPDirectives: AfterHash

70
.clang-tidy Normal file
View 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
View File

@ -0,0 +1,3 @@
.idea
cmake-build-*
ndppd.conf

9
CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 */