Compare commits

...

65 Commits

Author SHA1 Message Date
6cfacd05d8
Fix for broken strerr_r definition in alpine 2021-12-21 10:04:08 +00:00
Daniel Adolfsson
e01d67a864
Clarification 2020-05-22 16:20:26 +02:00
Daniel Adolfsson
2eb55c8e2b
Create README.md 2019-12-15 03:01:26 +01:00
Daniel Adolfsson
b7571fdaa7
Merge pull request #44 from torbennehmer/master
Fix issue #39, interface configuration
2019-05-06 21:17:39 +02:00
Daniel Adolfsson
97754c0a53
Merge pull request #47 from Lalufu/bugfix/disappearing-if
Handle disappearing interfaces
2019-05-06 21:12:08 +02:00
Ralf Ertzinger
b67f560309 Handle disappearing interfaces
When an interface disappears handle the resulting POLLERR
result from polling. Ignoring this will cause ndppd to go into
a hot loop.

This is a very simple fix that will make the daemon terminate.
2019-01-26 18:46:26 +01:00
root
caef398f85 Added init script working on Debian Jessie, just copy the file to
/etc/init.d/ndppd. Probably works on current stable as well, but
I couldn't test this so far.
2018-10-20 11:02:21 +02:00
root
13473e5259 Fix issue #39, configure interfaces after daemonization, as otherwise we
loose the ALLMULTI flag on the listening interfaces during the whole process.
Probable cause is the parent restoring the original state of the interface
flags before exitting.
2018-10-20 10:53:29 +02:00
Daniel Adolfsson
eb81b8f2d6 Merge pull request #36 from bgentry/remove-nd-proxy-from-makefile
remove cp nd-proxy from make install
2017-09-20 15:13:11 +02:00
Blake Gentry
8ed532809f
remove cp nd-proxy from make install
nd-proxy is not built by `make all`, so it shouldn't be copied by `make
install`.

Signed-off-by: Blake Gentry <blakesgentry@gmail.com>
2017-08-27 14:27:44 -07:00
Daniel Adolfsson
6ef7a57b2e Merge pull request #30 from john-sharratt/master
Feature to automatically wire the routes for a gateway
2017-08-07 13:18:13 +02:00
John Sharratt
a008fad5f4 Doing some debugging on an issue with the routes not working from gateway hosted IPs 2017-07-05 22:50:35 +02:00
John Sharratt
4ca86ef88e Added a fix to stop nasty NDP loops when being proactive on the NDP adverts 2017-07-05 01:24:29 +02:00
John Sharratt
e8e3739126 Added session keepalive with retries that will ensure the routes are kept up during high traffic when no proactive NDP soliciations are sent by the sender 2017-07-04 22:00:03 +02:00
John Sharratt
9341e7f0b7 Added some documentation on why autowire does not work with static and auto rules defined in the configuration 2017-07-04 21:39:39 +02:00
John Sharratt
1f50dc5b5a NDP proxy will now respond to Solicitation requests to gateway addresses that it holds on secondary interfaces 2017-07-04 20:17:53 +02:00
John Sharratt
a690d128e7 Added a global list of all the IP addresses that the interfaces of the local machine which will be used to cover some missing solicitation requests 2017-07-03 17:31:55 +02:00
John Sharratt
89130bf099 Now creating session on receiving NDP adverts even if no one has specifically asked for one yet thus ensuring the routes are created and that the latecy on proxy solicitation is kept much lower 2017-07-03 00:59:23 +02:00
John Sharratt
f6ef3fe494 Was touching all the sessions rather than the one that had an solicitation request causing none of the sessions to really expire 2017-07-02 21:50:23 +02:00
John Sharratt
f2d5804959 Added additional logging on system calls and ensured that an error message rather than an error code is returned for easier debugging 2017-07-02 15:53:35 +02:00
John Sharratt
0fd1dc0308 Added some additional debug information that help help track down what triggers a particular failure path 2017-07-02 15:26:19 +02:00
John Sharratt
5c56cd3a6d Now restoring the promiscuous state of the interface when the appliance terminates 2017-07-02 12:11:30 +02:00
John Sharratt
4f11b9a4f0 Now restoring the promiscuous state of the interface when the appliance terminates 2017-07-02 11:57:01 +02:00
John Sharratt
890d1a7821 Added a promiscuous mode that allows the proxy interface to also respond to queries send from the gateway (for instance when it needs to route to a WAN address thats local) 2017-07-02 10:59:19 +02:00
john-sharratt
4316aa58dd The manual had the wrong setting name for autowire 2017-07-02 10:29:40 +02:00
john-sharratt
b6b3487152 Changelog updated
Updated the changelog so that it reflects the pull request and describes what has changed in much more detail
2017-07-02 00:12:02 +02:00
John Sharratt
1a691255e3 Now properly exiting if any of the interfaces are not opened rather than silently continuing 2017-07-02 00:12:00 +02:00
John Sharratt
2f10118a5c Now daemonizing after the configuration rather than before so that more errors are caught on the command line rather than via an ASYNC process 2017-07-02 00:11:58 +02:00
John Sharratt
55ad4b11f0 Now only unwiring if the NDP proxy did the wiring in the first place 2017-07-02 00:11:56 +02:00
John Sharratt
701476417a Added some more debug information for what session is no longer valid when it times out 2017-07-02 00:11:51 +02:00
John Sharratt
2ceb687bdc Added some debugging when a valid session becomes invalid 2017-07-02 00:11:49 +02:00
John Sharratt
f8ea51ab5e Added automatic renewal of active sessions (what determines if its active can be improved in future) 2017-07-02 00:11:47 +02:00
John Sharratt
7e0948bf96 Added automatic unwiring when a session expires 2017-07-02 00:11:45 +02:00
John Sharratt
e11d1943e9 Added a command that updates the routing tables 2017-07-02 00:11:42 +02:00
John Sharratt
37181c81ca Added some more debug information for when a advert is received 2017-07-02 00:11:32 +02:00
John Sharratt
68240908bc Separated the TTL so that invalid sessions can be expired faster than valid sessions (DDOS protection and better error handling) 2017-07-02 00:11:30 +02:00
John Sharratt
aaaed4fce9 Added debug information that informs the autowire mode when creating a session 2017-07-02 00:11:24 +02:00
John Sharratt
8ed35f841f Put in the skeleton around the auto wiring functionality 2017-07-02 00:11:22 +02:00
Daniel Adolfsson
ca91245d3f Merge pull request #28 from meeuw/master
Add a systemd service file
2017-05-17 12:24:57 +02:00
Dick Marinus
f37e8eb33d Add a systemd service file 2017-05-17 07:36:52 +02:00
Daniel Adolfsson
eec9657b28 Merge pull request #23 from FRuffy/master
Fix makefile, switch ${OBS} and ${LIBS}.
2017-05-16 23:11:09 +02:00
Daniel Adolfsson
b30b654871 Add support for XSI-compliant strerror_r 2017-05-08 12:47:20 +02:00
Daniel Adolfsson
8a8f7b065c Fix compilation problems and issue #20
- Netlink is disabled per default (compile with WITH_ND_NETLINK)

- Automatic loading of routes is only done if at least one rule
  uses the "auto" setting.
2017-05-08 12:31:13 +02:00
FRuffy
3caf569a1f Fix makefile, switch ${OBS} and ${LIBS}. 2017-04-10 19:56:19 -07:00
Daniel Adolfsson
ce3815d954 Merge pull request #18 from alliedtelesis/master
Support DAD replies
2016-11-07 11:08:16 +01:00
Jason Rippon
ab7b2aa3bb Add functionality to reply to NS packets which are destined for a proxied interface.
Uses netlink sockets to learn the addresses on the proxied
interfaces so that NS packets can be replied to (with a NA)
when the address is present.

Signed-off-by: Carl Smith <carl.smith@alliedtelesis.co.nz>
2016-10-27 17:22:23 +13:00
Carl Smith
a35def5b52 Make nd-proxy handle the interface being shutdown gracefully
Signed-off-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
2016-10-19 11:41:30 +13:00
Carl Smith
df16c053b0 New nd-proxy for RS/RA proxying
Signed-off-by: Blair Steven <blair.steven@alliedtelesis.co.nz>
2016-09-22 16:06:38 +12:00
Blair Steven
cf244c10f7 Send proxied NA to the correct destination address
We need to send the NA back to the All Nodes Multicast address
rather than attempting to route to "::".

Signed-off-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
2016-09-21 15:09:36 +12:00
Isaac Lee
8afd356df3 ND proxy support for dynamic interfaces
Dynamic interfaces, such as tunnel and ppp, do not exist
in the kernel until it's fully configured. If nd proxy
is configured on such an interface, the ndppd config for
this interface should be written only when the interface's
fully configured (ie, having a valid ifindex value in apteryx).

The patch ensures that the ndppd config only contains interfaces
that have a valid ifindex. This is done by watching the interface
ifindex node in apteryx and trigger the ndppd config update callback
when ifindex has changed.

To make ndppd not exit when the config file contains an interface
that is not yet created, ndppd is patched to treat fd-bind error
as a valid return code so that the program does not try to
proxy the interface and will continue to run.

Signed-off-by: Scott Parlane <scott.parlane@alliedtelesis.co.nz>
2016-09-21 15:09:14 +12:00
Daniel Adolfsson
771f9ca29d Various changes (referencing issue #6);
- Fix a bug where re-reading routes would close the interfaces

- Allow unicast solicitation messages (experimental)
2016-05-17 08:55:06 +02:00
Daniel Adolfsson
9709759270 Remove the default rule behavior 'static', this must now be specified.
Bump version to 0.2.5
2016-04-18 19:14:27 +02:00
Daniel Adolfsson
9499745013 Add cerrno include to logger.cc 2016-04-18 18:45:43 +02:00
Daniel Adolfsson
cb23cdbeac Defer the configuration until after daemonized 2016-04-18 15:49:02 +02:00
Daniel Adolfsson
96ab05dade When daemonizing, change umask to 0 and change working directory to /.
Patch provided by Tim Bruylants (https://github.com/tbr)
2016-04-18 09:37:56 +02:00
Daniel Adolfsson
e49b71c0b7 Add some additional logging to allmulti() 2016-04-18 09:32:03 +02:00
Daniel Adolfsson
f19fa4be6a Fix a cast so it can be compiled with gcc-6 2016-04-08 13:32:22 +02:00
Daniel Adolfsson
b8b2d401b9 Preparation for 0.2.4 2015-10-13 15:25:22 +04:00
Daniel Adolfsson
4a837c511c Update README with the correct sample conf
It is "ndppd.conf-dist", not "ndppd.conf.example"
2015-10-11 03:11:19 +04:00
Daniel Adolfsson
e6f2e0a52e Update Website/Git URIs 2015-10-11 02:53:00 +04:00
Daniel Adolfsson
baec0bf028 Merge pull request #8 from nirgal/patch-1
Use standard C pre-processor flags from environement
2015-10-11 02:49:41 +04:00
nirgal
87f989026d Use standard C pre-processor flags in environement
This allows build environment to set standard C pre-processor "CPPFLAGS".
2015-10-09 11:36:03 +02:00
Daniel Adolfsson
e2e44ec5ed Merge pull request #5 from szatam/master
In order to get the correct pid, we should daemonize before writing the ...
2015-01-28 16:45:48 +04:00
Tamas Szabo
6b08f63fff In order to get the correct pid, we should daemonize before writing the pidfile 2014-12-19 11:44:10 +02:00
Daniel Adolfsson
00da8bf7ba Fix issue #2 - ndppd dæmonizes too early 2013-02-12 13:26:27 +01:00
28 changed files with 2395 additions and 257 deletions

View File

@ -1,3 +1,65 @@
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
@ -10,7 +72,7 @@
* New "auto" configuration to detect outgoing interface, for forwarding
Neighbor Solicitation Messages.
* Improved logging.
* Bug fixes related to memory management.

View File

@ -9,11 +9,17 @@ CXX ?= g++
GZIP ?= /bin/gzip
MANDIR ?= ${DESTDIR}${PREFIX}/share/man
SBINDIR ?= ${DESTDIR}${PREFIX}/sbin
PKG_CONFIG ?= pkg-config
LIBS =
OBJS = src/logger.o src/ndppd.o src/iface.o src/proxy.o src/address.o \
src/rule.o src/session.o src/conf.o src/route.o
src/rule.o src/session.o src/conf.o src/route.o
ifdef WITH_ND_NETLINK
LIBS = `${PKG_CONFIG} --libs glib-2.0 libnl-3.0 libnl-route-3.0` -pthread
CPPFLAGS = `${PKG_CONFIG} --cflags glib-2.0 libnl-3.0 libnl-route-3.0`
OBJ = ${OBJ} src/nd-netlink.o
endif
all: ndppd ndppd.1.gz ndppd.conf.5.gz
@ -31,10 +37,13 @@ ndppd.conf.5.gz:
${GZIP} < ndppd.conf.5 > ndppd.conf.5.gz
ndppd: ${OBJS}
${CXX} -o ndppd ${LDFLAGS} ${LIBS} ${OBJS}
${CXX} -o ndppd ${LDFLAGS} ${OBJS} ${LIBS}
nd-proxy: nd-proxy.c
${CXX} -o nd-proxy -Wall -Werror ${LDFLAGS} `${PKG_CONFIG} --cflags glib-2.0` nd-proxy.c `${PKG_CONFIG} --libs glib-2.0`
.cc.o:
${CXX} -c $(CXXFLAGS) -o $@ $<
${CXX} -c ${CPPFLAGS} $(CXXFLAGS) -o $@ $<
clean:
rm -f ndppd ndppd.conf.5.gz ndppd.1.gz ${OBJS}
rm -f ndppd ndppd.conf.5.gz ndppd.1.gz ${OBJS} nd-proxy

23
README
View File

@ -1,24 +1,24 @@
ndppd - NDP Proxy Daemon
Version 0.2.2
Version 0.2.5
------------------------------------------------------------------------
1. Legal
------------------------------------------------------------------------
ndppd - NDP Proxy Daemon
Copyright (C) 2011 Daniel Adolfsson <daniel@priv.nu>
Copyright (C) 2011-2016 Daniel Adolfsson <daniel@priv.nu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
@ -93,7 +93,7 @@
If you want to enable debugging, you can type:
make DEBUG=1 all
Note that this version of the binary is much bigger, and the daemon
produces a lot of messages.
@ -101,8 +101,8 @@
5. Usage
------------------------------------------------------------------------
Read through 'ndppd.conf.example' for guidelines how to configure
the daemon.
Read through 'ndppd.conf-dist' for guidelines and examples how to
configure the daemon.
Usage: ndppd [-d] [-c <config>] [-p <pidfile>]
@ -115,7 +115,7 @@
-d Daemonize the process, putting it in the background.
Also enables syslogging.
-v Increase logging verbosity. Can be used several times in
order to increase even further.
@ -125,10 +125,9 @@
Contact : Daniel Adolfsson <daniel@priv.nu>
Website : http://www.priv.nu/projects/ndppd
Website : https://github.com/DanielAdolfsson/ndppd
Git : git://github.com/Tuhox/ndppd.git
https://github.com/Tuhox/ndppd
Git : git://github.com/DanielAdolfsson/ndppd.git
If you want to report a bug, you can either send me a mail directly,
or submit an issue on github.com.

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# NDPPD
***ndppd***, or ***NDP Proxy Daemon***, is a daemon that proxies *neighbor discovery* messages. It listens for *neighbor solicitations* on a
specified interface and responds with *neighbor advertisements* - as described in **RFC 4861** (section 7.2).
## Current status
Version 0.x is in maintenance, and is being replaced by `1.0-devel` which you can find [here](https://github.com/DanielAdolfsson/ndppd/tree/1.0-devel). `1.0` is not yet stable enough to be used in production, and I currently have no estimate when it is. Feel free to try it out if you like.
Latest stable release is 0.2.5:
- [Download](https://github.com/DanielAdolfsson/ndppd/releases/tag/0.2.5)
- [README](https://github.com/DanielAdolfsson/ndppd/blob/0.2.5/README)

550
nd-proxy.c Normal file
View File

@ -0,0 +1,550 @@
/**
* @file nd-proxy.c
*
* Copyright 2016, Allied Telesis Labs New Zealand, Ltd
*
* +---------+
* If A | | If B
* A-----------------| PROXY |-----------------B
* | |
* +---------+
* IPv6: A IPv6: PA IPv6: PB IPv6: B
* L2: a L2: pa L2: pb L2: b
*
* RS/RA proxy
* RS
* -------------------->
* L3src=A, L3dst=AllR L3src=A, L3dst=AllR
* L2src=a, L2dst=allr, SLL=a L2src=pb, L2dst=allr, SLL=pb
*
* RA
* <--------------------
* L3src=B, L3dst=AllN L3src=B, L3dst=AllN
* L2src=pa, L2dst=alln, SLL=pa L2src=b, L2dst=alln, SLL=b
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <glib.h>
#include <glib-unix.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <linux/filter.h>
/* Mode */
#define PROXY_RS (1 << 0)
#define PROXY_RA (1 << 1)
#define PROXY_NS (1 << 2)
#define PROXY_NA (1 << 3)
#define PROXY_RD (1 << 4)
/* Debug macros */
#define DEBUG(fmt, args...) if (debug) printf (fmt, ## args)
#define ERROR(fmt, args...) \
{ \
syslog(LOG_ERR, fmt, ## args); \
fprintf(stderr, fmt, ## args); \
}
/* Proxy interface */
typedef struct _iface_t {
char *name;
uint32_t flags;
int ifindex;
uint8_t hwaddr[ETH_ALEN];
int fd;
guint src;
} iface_t;
/* Globals */
static bool debug = false;
static GList *ifaces = NULL;
/* Find the specified option in the ICMPv6 message */
static struct nd_opt_hdr *
find_option(struct icmp6_hdr *icmp6_hdr, int len, uint8_t type)
{
struct nd_opt_hdr *nd_opt;
int icmp_hlen;
/* Each ND type has a different offest to the options */
switch (icmp6_hdr->icmp6_type) {
case ND_ROUTER_SOLICIT:
icmp_hlen = sizeof(struct nd_router_solicit);
break;
case ND_ROUTER_ADVERT:
icmp_hlen = sizeof(struct nd_router_advert);
break;
case ND_NEIGHBOR_SOLICIT:
icmp_hlen = sizeof(struct nd_neighbor_solicit);
break;
case ND_NEIGHBOR_ADVERT:
icmp_hlen = sizeof(struct nd_neighbor_advert);
break;
case ND_REDIRECT:
icmp_hlen = sizeof(struct nd_redirect);
break;
default:
return NULL;
}
/* Find the option */
nd_opt = (struct nd_opt_hdr *)((uint8_t *)icmp6_hdr + icmp_hlen);
len -= icmp_hlen;
while (len > 0) {
int opt_len = nd_opt->nd_opt_len * 8;
if (nd_opt->nd_opt_type == type)
return nd_opt;
nd_opt = (struct nd_opt_hdr *)((uint8_t *)nd_opt +
sizeof(struct nd_opt_hdr) + opt_len);
len -= (sizeof(struct nd_opt_hdr) + opt_len);
}
return NULL;
}
/* Update the SLLA option in the packet (and checksum) */
static void
update_slla_option(struct icmp6_hdr *icmp6_hdr, int len, uint8_t *mac)
{
struct nd_opt_hdr *nd_opt;
/* Find the "source link-layer address" option */
nd_opt = find_option(icmp6_hdr, len, ND_OPT_SOURCE_LINKADDR);
/* Update the slla if we found it */
if (nd_opt) {
/* Option data is the mac address - it is always 16-bit aligned */
uint8_t *slla = (uint8_t *)nd_opt + sizeof(struct nd_opt_hdr);
/* Update ICMPv6 header checksum based on the old and new mac adddress */
uint16_t *omac = (uint16_t *)slla;
uint16_t *nmac = (uint16_t *)mac;
int i;
for (i = 0; i < ETH_ALEN / 2; i++) {
uint16_t hc_complement = ~ntohs(icmp6_hdr->icmp6_cksum);
uint16_t m_complement = ~ntohs(omac[i]);
uint16_t m_prime = ntohs(nmac[i]);
uint32_t sum = hc_complement + m_complement + m_prime;
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
icmp6_hdr->icmp6_cksum = htons(~((uint16_t)sum));
}
/* Copy the outgoing interface's hw addr into the
* "source link-layer address" option in the pkt. */
memcpy(slla, mac, ETH_ALEN);
}
}
/* Proxying of both RS and RA */
static void
proxy_rsra(iface_t *iface, uint8_t *msg, int len)
{
struct ether_header *eth_hdr;
struct ip6_hdr *ip6;
struct icmp6_hdr *icmp6_hdr;
struct sockaddr_ll socket_address;
/* Parse the packet */
eth_hdr = (struct ether_header *)msg;
ip6 = (struct ip6_hdr *)(msg + sizeof(struct ether_header));
icmp6_hdr = (struct icmp6_hdr *)(msg + sizeof(struct ether_header) +
sizeof(struct ip6_hdr));
DEBUG("Tx(%s): %s\n", iface->name,
icmp6_hdr->icmp6_type == ND_ROUTER_SOLICIT ?
"ND_ROUTER_SOLICIT" : "ND_ROUTER_ADVERT");
/* Avoid proxying spoofed packets */
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src)) {
DEBUG("Tx(%s): Ignoring RS/RA from spoofed address\n", iface->name);
return;
}
/* RS should be sent to "All Routers Address" FF02::2 */
/* RA should be sent to "All Nodes Address" FF02::1 */
/* Can only proxy to multicast L2 destinations 33:33:.. */
if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
(eth_hdr->ether_dhost[0] != 0x33 && eth_hdr->ether_dhost[1] != 0x33)) {
DEBUG("Tx(%s): Ignoring RS/RA to non-multicast address\n", iface->name);
return;
}
/* Copy the outgoing interface's hw addr into the
* "source link-layer address" option in the pkt */
update_slla_option(icmp6_hdr, len - ((uint8_t *)icmp6_hdr - msg), iface->hwaddr);
/* Copy the outgoing interface's hw addr into the
* MAC source address in the pkt. */
memcpy((uint8_t *)(eth_hdr->ether_shost), iface->hwaddr, ETH_ALEN);
/* Send the packet */
socket_address.sll_ifindex = iface->ifindex;
socket_address.sll_halen = ETH_ALEN;
memcpy((uint8_t *)socket_address.sll_addr, iface->hwaddr, ETH_ALEN);
if (sendto (iface->fd, msg, len, 0, (struct sockaddr *)&socket_address,
sizeof(struct sockaddr_ll)) < 0) {
ERROR("Tx(%s): Failed to send packet\n", iface->name);
return;
}
}
static gboolean
handle_fd(gint fd, GIOCondition condition, gpointer data)
{
iface_t *iface = (iface_t *)data;
struct msghdr mhdr;
struct iovec iov;
struct sockaddr_ll t_saddr;
uint8_t msg[4096];
int size = 4096;
int len;
/* Receive a packet */
iov.iov_len = size;
iov.iov_base = (caddr_t)msg;
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_name = (caddr_t)&t_saddr;
mhdr.msg_namelen = sizeof(struct sockaddr);
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
if ((len = recvmsg(fd, &mhdr, 0)) < 0) {
DEBUG("Rx(%s):Interface has gone away\n", iface->name);
return true;
}
/* Check we have at least the icmp header */
if ((size_t) len < (ETH_HLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr))) {
ERROR("Rx(%s): Ignoring short packet (%d bytes)\n", iface->name, len);
return true;
}
struct icmp6_hdr *icmp6_hdr = (struct icmp6_hdr *)(msg + ETH_HLEN + sizeof(struct ip6_hdr));
uint8_t icmp6_type = icmp6_hdr->icmp6_type;
switch (icmp6_type) {
case ND_ROUTER_SOLICIT:
DEBUG("Rx(%s): ND_ROUTER_SOLICIT\n", iface->name);
if (iface->flags & PROXY_RS) {
GList *iter;
for (iter = ifaces; iter; iter = g_list_next(iter)) {
iface_t *oiface = (iface_t *)iter->data;
if (oiface != iface)
proxy_rsra(oiface, msg, len);
}
}
break;
case ND_ROUTER_ADVERT:
DEBUG("Rx(%s): ND_ROUTER_ADVERT\n", iface->name);
if (iface->flags & PROXY_RA) {
GList *iter;
for (iter = ifaces; iter; iter = g_list_next(iter)) {
iface_t *oiface = (iface_t *)iter->data;
if (oiface != iface)
proxy_rsra(oiface, msg, len);
}
}
break;
case ND_NEIGHBOR_SOLICIT:
case ND_NEIGHBOR_ADVERT:
case ND_REDIRECT:
default:
DEBUG("Rx(%s): ignoring ICMPv6 packets of type %d\n", iface->name, icmp6_type);
break;
}
return true;
}
static char *flags_to_string(uint32_t flags)
{
static char sbuffer[256];
sbuffer[0] = '\0';
if (flags == 0)
sprintf(sbuffer, "no packets");
if (flags & PROXY_NS)
sprintf(sbuffer + strlen(sbuffer), "%sNS",
strlen(sbuffer) ? "," : "");
if (flags & PROXY_NA)
sprintf(sbuffer + strlen(sbuffer), "%sNA",
strlen(sbuffer) ? "," : "");
if (flags & PROXY_RS)
sprintf(sbuffer + strlen(sbuffer), "%sRS",
strlen(sbuffer) ? "," : "");
if (flags & PROXY_RA)
sprintf(sbuffer + strlen(sbuffer), "%sRA",
strlen(sbuffer) ? "," : "");
if (flags & PROXY_RD)
sprintf(sbuffer + strlen(sbuffer), "%sRD",
strlen(sbuffer) ? "," : "");
return sbuffer;
}
static struct sock_filter bpf_filter[] = {
/* Load the ether_type. */
BPF_STMT(BPF_LD | BPF_H | BPF_ABS,
offsetof(struct ether_header, ether_type)),
/* Bail if it's* not* ETHERTYPE_IPV6. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 9),
/* Load the next header type. */
BPF_STMT(BPF_LD | BPF_B | BPF_ABS,
sizeof(struct ether_header) + offsetof(struct ip6_hdr,
ip6_nxt)),
/* Bail if it's* not* IPPROTO_ICMPV6. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 7),
/* Load the ICMPv6 type. */
BPF_STMT(BPF_LD | BPF_B | BPF_ABS,
sizeof(struct ether_header) + sizeof(struct ip6_hdr) +
offsetof(struct icmp6_hdr, icmp6_type)),
/* Bail if it's* not* ND */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_SOLICIT, 4, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_REDIRECT, 0, 1),
/* Keep packet. */
BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1),
/* Drop packet. */
BPF_STMT(BPF_RET | BPF_K, 0)
};
static void iface_open(gpointer data, gpointer user)
{
iface_t *iface = (iface_t *)data;
struct sockaddr_ll lladdr;
struct sock_fprog fprog;
struct ifreq ifr;
int on = 1;
int fd;
DEBUG("Open(%s): %s\n", iface->name, flags_to_string(iface->flags));
/* Check the interface exists by getting its ifindex */
iface->ifindex = if_nametoindex(iface->name);
if (!iface->ifindex) {
ERROR("Open(%s): Could not find interface\n", iface->name);
exit(-1);
}
/* Create raw socket for tx/rx of IPv6 packets */
if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IPV6))) < 0) {
ERROR("Open(%s): Unable to create socket\n", iface->name);
exit(-1);
}
/* Bind the socket to the specified interface */
memset(&lladdr, 0, sizeof(struct sockaddr_ll));
lladdr.sll_family = AF_PACKET;
lladdr.sll_protocol = htons(ETH_P_IPV6);
lladdr.sll_ifindex = iface->ifindex;
if (bind(fd, (struct sockaddr *)&lladdr, sizeof(struct sockaddr_ll)) < 0) {
close(fd);
ERROR("Open(%s): Failed to bind to interface\n", iface->name);
exit(-1);
}
/* Set the socket non-blocking */
if (ioctl(fd, FIONBIO, (char *)&on) < 0) {
close(fd);
ERROR("Open(%s): Failed to make interface non-blocking\n", iface->name);
exit(-1);
}
/* Setup a filter to only receive ND packets */
fprog.len = sizeof(bpf_filter) / sizeof(bpf_filter[0]);
fprog.filter = bpf_filter;
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) {
close(fd);
ERROR("Open(%s): Failed to set filter for ND packets\n", iface->name);
exit(-1);
}
/* Enable all multicast for this interface */
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, iface->name, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
close(fd);
ERROR("Open(%s): Failed to get flags for interface\n", iface->name);
exit(-1);
}
ifr.ifr_flags |= IFF_ALLMULTI;
if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) {
close(fd);
ERROR("Open(%s): Failed to set flags for interface\n", iface->name);
exit(-1);
}
/* Get the hwaddr of the interface */
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, iface->name, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
ERROR("Open(%s): Failed to get interface hwaddr\n", iface->name);
exit(-1);
}
memcpy(iface->hwaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
/* Watch for packets */
iface->fd = fd;
iface->src = g_unix_fd_add(fd, G_IO_IN, handle_fd, iface);
}
static void iface_close(gpointer data)
{
iface_t *iface = (iface_t *)data;
DEBUG("Close(%s)\n", iface->name);
g_source_remove(iface->src);
close(iface->fd);
free(iface->name);
free(iface);
}
static iface_t *parse_interface(char *desc)
{
char *name = NULL;
uint32_t flags = PROXY_RS | PROXY_RA;
char *pflags = strchr(desc, ':');
if (pflags) {
char *token = strtok(pflags + 1, ",");
flags = 0;
while (token != NULL) {
if (strcmp("NS", token) == 0)
flags |= PROXY_NS;
else if (strcmp("NA", token) == 0)
flags |= PROXY_NA;
else if (strcmp("RA", token) == 0)
flags |= PROXY_RA;
else if (strcmp("RS", token) == 0)
flags |= PROXY_RS;
else if (strcmp("RD", token) == 0)
flags |= PROXY_RD;
else
return NULL;
token = strtok(NULL, ",");
}
name = strndup(desc, pflags - desc);
} else {
name = strdup(desc);
}
iface_t *iface = (iface_t *)g_malloc0(sizeof(iface_t));
iface->name = name;
iface->flags = flags;
iface->fd = -1;
iface->src = 0;
return iface;
}
static gboolean termination_handler(gpointer arg1)
{
GMainLoop *loop = (GMainLoop *) arg1;
g_main_loop_quit(loop);
return false;
}
void help(char *app_name)
{
printf("Usage: %s [-h] [-b] [-d] -i <interface>[:<type>[,<type>]..]\n"
" -h show this help\n"
" -b background mode\n"
" -d enable verbose debug\n"
" -i proxy [NS,NA,RS,RA,RD] messages received on <interface>\n"
"\n" "e.g %s -i eth1:RS -i eth2:RA\n", app_name, app_name);
}
int main(int argc, char *argv[])
{
int i = 0;
bool background = false;
GMainLoop *loop = NULL;
/* Parse options */
while ((i = getopt(argc, argv, "hdbi:")) != -1) {
switch (i) {
case 'd':
debug = true;
background = false;
break;
case 'b':
background = true;
break;
case 'i':
{
iface_t *iface = parse_interface(optarg);
if (!iface) {
help(argv[0]);
ERROR("ERROR: Invalid interface specification (%s)\n", optarg);
return 0;
}
ifaces = g_list_prepend(ifaces, iface);
break;
}
case '?':
case 'h':
default:
help(argv[0]);
return 0;
}
}
/* Check required */
if (g_list_length(ifaces) < 2) {
help(argv[0]);
ERROR("ERROR: Require at least 2 interfaces.\n");
return 0;
}
/* Daemonize */
if (background && fork() != 0) {
/* Parent */
return 0;
}
/* Main loop instance */
loop = g_main_loop_new(NULL, true);
/* Handle SIGTERM/SIGINT/SIGPIPE gracefully */
g_unix_signal_add(SIGINT, termination_handler, loop);
g_unix_signal_add(SIGTERM, termination_handler, loop);
/* Startup */
g_list_foreach(ifaces, iface_open, NULL);
/* Loop while not terminated */
g_main_loop_run(loop);
/* Shutdown */
g_list_free_full(ifaces, iface_close);
/* Free the glib main loop */
if (loop)
g_main_loop_unref(loop);
return 0;
}

21
ndppd-init-debian-jessi Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# kFreeBSD do not accept scripts as interpreters, using #!/bin/sh and sourcing.
if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then
set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script
fi
### BEGIN INIT INFO
# Provides: ndppd
# Required-Start: $remote_fs $syslog $network
# Required-Stop: $remote_fs $syslog $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: ndppd init script
# Description: NDP Proxy Daemon init script
### END INIT INFO
# Author: Torben Nehmer <torben+ndppd@nehmer.net>
DESC="NDP Proxy Daemon"
PIDFILE=/run/ndppd.pid
DAEMON=/usr/local/sbin/ndppd
DAEMON_ARGS="-d -p $PIDFILE"

View File

@ -4,6 +4,12 @@
route-ttl 30000
# address-ttl <integer> (NEW)
# This tells 'ndppd' how often to reload the IP address file /proc/net/if_inet6
# Default value is '30000' (30 seconds).
address-ttl 30000
# proxy <interface>
# This sets up a listener, that will listen for any Neighbor Solicitation
# messages, and respond to them according to a set of rules (see below).
@ -22,6 +28,36 @@ proxy eth0 {
# invalidating the entry, in milliseconds. Default value is '500'.
timeout 500
# autowire <yes|no|true|false>
# Controls whether ndppd will automatically create host entries
# in the routing tables when it receives Neighbor Advertisements on a
# listening interface. The the default value is no.
# Note: Autowire will ignore all rules with 'auto' or 'static' given it
# is expected that the routes are already defined for these paths
autowire no
# keepalive <yes|no|true|false>
# Controls whether ndppd will automatically attempt to keep routing
# sessions alive by actively sending out NDP Solicitations before the the
# session is expired. The the default value is yes.
keepalive yes
# retries <integer>
# Number of times a NDP Solicitation will be sent out before the daemon
# considers a route unreachable. The default value is 3
retries 3
# promiscuous <yes|no|true|false>
# Controls whether ndppd will put the proxy listening interface into promiscuous
# mode and hence will react to inbound and outbound NDP commands. This is
# required for machines behind the gateway to talk to each other in more
# complex topology scenarios. The the default value is no.
promiscuous no
# ttl <integer>
# Controls how long a valid or invalid entry remains in the cache, in
@ -55,6 +91,13 @@ proxy eth0 {
auto
# autovia <yes|no|true|false>
# Any addresses updated using NDP advertisments will use a gateway to
# route traffic on this particular interface (only works wiith the iface
# rule type). Default is no
autovia no
# Note that before version 0.2.2 of 'ndppd', if you didn't choose a
# method, it defaulted to 'static'. For compatibility reasons we choose
# to keep this behavior - for now (it may be removed in a future version).

View File

@ -48,6 +48,20 @@ Controls how long
.B ndppd
will cache an entry. This is in milliseconds, and the default value
is 30000 (30 seconds).
.IP "autowire <yes|no>"
Controls whether
.B ndppd
will automatically create host entries in the routing tables when
.B ndppd receives Neighbor Advertisements on a listening interface.
The default value is no.
.IP "promiscuous <yes|no>"
Controls whether
.B ndppd
will put the proxy listening interface into promiscuous mode and
hence will react to inbound and outbound NDP commands. This is
required for machines behind the gateway to talk to each other in
more complex topology scenarios.
The the default value is no.
.IP "timeout <value>"
Controls how long
.B ndppd

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

View File

@ -15,6 +15,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <string>
#include <vector>
#include <fstream>
#include <list>
#include <map>
#include <cstring>
@ -27,9 +29,16 @@
#include "ndppd.h"
#include "address.h"
#include "route.h"
NDPPD_NS_BEGIN
std::list<ptr<route> > address::_addresses;
int address::_ttl;
int address::_c_ttl;
address::address()
{
reset();
@ -48,6 +57,19 @@ address::address(const address& addr)
_mask.s6_addr32[3] = addr._mask.s6_addr32[3];
}
address::address(const ptr<address>& addr)
{
_addr.s6_addr32[0] = addr->_addr.s6_addr32[0];
_addr.s6_addr32[1] = addr->_addr.s6_addr32[1];
_addr.s6_addr32[2] = addr->_addr.s6_addr32[2];
_addr.s6_addr32[3] = addr->_addr.s6_addr32[3];
_mask.s6_addr32[0] = addr->_mask.s6_addr32[0];
_mask.s6_addr32[1] = addr->_mask.s6_addr32[1];
_mask.s6_addr32[2] = addr->_mask.s6_addr32[2];
_mask.s6_addr32[3] = addr->_mask.s6_addr32[3];
}
address::address(const std::string& str)
{
parse_string(str);
@ -110,6 +132,21 @@ bool address::operator!=(const address& addr) const
((_addr.s6_addr32[3] ^ addr._addr.s6_addr32[3]) & _mask.s6_addr32[3]));
}
bool address::is_empty() const
{
if (_addr.s6_addr32[0] == 0 &&
_addr.s6_addr32[1] == 0 &&
_addr.s6_addr32[2] == 0 &&
_addr.s6_addr32[3] == 0 &&
_mask.s6_addr32[0] == 0xffffffff &&
_mask.s6_addr32[1] == 0xffffffff &&
_mask.s6_addr32[2] == 0xffffffff &&
_mask.s6_addr32[3] == 0xffffffff)
return true;
return false;
}
void address::reset()
{
_addr.s6_addr32[0] = 0;
@ -299,7 +336,93 @@ bool address::is_multicast() const
bool address::is_unicast() const
{
if (_addr.s6_addr32[2] == 0 &&
_addr.s6_addr32[3] == 0)
return false;
return _addr.s6_addr[0] != 0xff;
}
void address::add(const address& addr, const std::string& ifname)
{
ptr<route> rt(new route(addr, ifname));
// logger::debug() << "address::create() addr=" << addr << ", ifname=" << ifname;
_addresses.push_back(rt);
}
std::list<ptr<route> >::iterator address::addresses_begin()
{
return _addresses.begin();
}
std::list<ptr<route> >::iterator address::addresses_end()
{
return _addresses.end();
}
void address::load(const std::string& path)
{
// Hack to make sure the addresses are not freed prematurely.
std::list<ptr<route> > tmp_addresses(_addresses);
_addresses.clear();
logger::debug() << "reading IP addresses";
try {
std::ifstream ifs;
ifs.exceptions(std::ifstream::badbit | std::ifstream::failbit);
ifs.open(path.c_str(), std::ios::in);
ifs.exceptions(std::ifstream::badbit);
while (!ifs.eof()) {
char buf[1024];
ifs.getline(buf, sizeof(buf));
if (ifs.gcount() < 53) {
if (ifs.gcount() > 0)
logger::debug() << "skipping entry (size=" << ifs.gcount() << ")";
continue;
}
address addr;
if (route::hexdec(buf, (unsigned char* )&addr.addr(), 16) != 16) {
logger::warning() << "failed to load address (" << buf << ")";
continue;
}
addr.prefix(128);
std::string iface = route::token(buf + 45);
address::add(addr, iface);
logger::debug() << "found local addr=" << addr << ", iface=" << iface;
}
} catch (std::ifstream::failure e) {
logger::warning() << "Failed to parse IPv6 address data from '" << path << "'";
logger::error() << e.what();
}
logger::debug() << "completed IP addresses load";
}
void address::update(int elapsed_time)
{
if ((_c_ttl -= elapsed_time) <= 0) {
load("/proc/net/if_inet6");
_c_ttl = _ttl;
}
}
int address::ttl()
{
return _ttl;
}
void address::ttl(int ttl)
{
_ttl = ttl;
}
NDPPD_NS_END

View File

@ -16,6 +16,7 @@
#pragma once
#include <string>
#include <list>
#include <netinet/ip6.h>
#include "ndppd.h"
@ -24,15 +25,24 @@ NDPPD_NS_BEGIN
class iface;
class route;
class address {
public:
address();
address(const address& addr);
address(const ptr<address>& addr);
address(const std::string& str);
address(const char* str);
address(const in6_addr& addr);
address(const in6_addr& addr, const in6_addr& mask);
address(const in6_addr& addr, int prefix);
static void update(int elapsed_time);
static int ttl();
static void ttl(int ttl);
struct in6_addr& addr();
@ -46,6 +56,8 @@ public:
bool operator!=(const address& addr) const;
void reset();
bool is_empty() const;
const std::string to_string() const;
@ -60,8 +72,22 @@ public:
bool is_multicast() const;
operator std::string() const;
static void add(const address& addr, const std::string& ifname);
static void load(const std::string& path);
static std::list<ptr<route> >::iterator addresses_begin();
static std::list<ptr<route> >::iterator addresses_end();
private:
static int _ttl;
static int _c_ttl;
static std::list<ptr<route> > _addresses;
struct in6_addr _addr, _mask;
};

View File

@ -34,11 +34,13 @@
#include <linux/filter.h>
#include <errno.h>
#include <string>
#include <vector>
#include <map>
#include "ndppd.h"
#include "route.h"
NDPPD_NS_BEGIN
@ -64,13 +66,19 @@ iface::~iface()
if (_prev_allmulti >= 0) {
allmulti(_prev_allmulti);
}
if (_prev_promiscuous >= 0) {
promiscuous(_prev_promiscuous);
}
close(_pfd);
}
_map_dirty = true;
_serves.clear();
_parents.clear();
}
ptr<iface> iface::open_pfd(const std::string& name)
ptr<iface> iface::open_pfd(const std::string& name, bool promiscuous)
{
int fd = 0;
@ -147,7 +155,7 @@ ptr<iface> iface::open_pfd(const std::string& name)
// Bail if it's* not* ND_NEIGHBOR_SOLICIT.
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 0, 1),
// Keep packet.
BPF_STMT(BPF_RET | BPF_K, -1),
BPF_STMT(BPF_RET | BPF_K, (u_int32_t)-1),
// Drop packet.
BPF_STMT(BPF_RET | BPF_K, 0)
};
@ -168,6 +176,13 @@ ptr<iface> iface::open_pfd(const std::string& name)
// Eh. Allmulti.
ifa->_prev_allmulti = ifa->allmulti(1);
// Eh. Promiscuous
if (promiscuous == true) {
ifa->_prev_promiscuous = ifa->promiscuous(1);
} else {
ifa->_prev_promiscuous = -1;
}
_map_dirty = true;
@ -212,23 +227,29 @@ ptr<iface> iface::open_ifd(const std::string& name)
if (ioctl(fd, SIOCGIFHWADDR,& ifr) < 0) {
close(fd);
logger::error() << "Failed to detect link-layer address for interface '" << name << "'";
logger::error()
<< "Failed to detect link-layer address for interface '"
<< name << "'";
return ptr<iface>();
}
logger::debug() << "fd=" << fd << ", hwaddr=" << ether_ntoa((const struct ether_addr* )&ifr.ifr_hwaddr.sa_data);;
logger::debug()
<< "fd=" << fd << ", hwaddr="
<< ether_ntoa((const struct ether_addr* )&ifr.ifr_hwaddr.sa_data);
// Set max hops.
int hops = 255;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,& hops, sizeof(hops)) < 0) {
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops,
sizeof(hops)) < 0) {
close(fd);
logger::error() << "iface::open_ifd() failed IPV6_MULTICAST_HOPS";
return ptr<iface>();
}
if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,& hops, sizeof(hops)) < 0) {
if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops,
sizeof(hops)) < 0) {
close(fd);
logger::error() << "iface::open_ifd() failed IPV6_UNICAST_HOPS";
return ptr<iface>();
@ -238,9 +259,11 @@ ptr<iface> iface::open_ifd(const std::string& name)
int on = 1;
if (ioctl(fd, FIONBIO, (char* )&on) < 0) {
if (ioctl(fd, FIONBIO, (char*)&on) < 0) {
close(fd);
logger::error() << "Failed to switch to non-blocking on interface '" << name << "'";
logger::error()
<< "Failed to switch to non-blocking on interface '"
<< name << "'";
return ptr<iface>();
}
@ -248,7 +271,7 @@ ptr<iface> iface::open_ifd(const std::string& name)
struct icmp6_filter filter;
ICMP6_FILTER_SETBLOCKALL(&filter);
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT,& filter);
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER,& filter, sizeof(filter)) < 0) {
logger::error() << "Failed to set filter";
@ -278,7 +301,7 @@ ptr<iface> iface::open_ifd(const std::string& name)
return ifa;
}
ssize_t iface::read(int fd, struct sockaddr* saddr, uint8_t* msg, size_t size)
ssize_t iface::read(int fd, struct sockaddr* saddr, ssize_t saddr_size, uint8_t* msg, size_t size)
{
struct msghdr mhdr;
struct iovec iov;
@ -292,18 +315,21 @@ ssize_t iface::read(int fd, struct sockaddr* saddr, uint8_t* msg, size_t size)
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_name = (caddr_t)saddr;
mhdr.msg_namelen = sizeof(struct sockaddr);
mhdr.msg_namelen = saddr_size;
mhdr.msg_iov =& iov;
mhdr.msg_iovlen = 1;
if ((len = recvmsg(fd,& mhdr, 0)) < 0)
{
logger::error() << "iface::read() failed! error=" << logger::err() << ", ifa=" << name();
return -1;
}
logger::debug() << "iface::read() ifa=" << name() << ", len=" << len;
if (len < sizeof(struct icmp6_hdr))
return -1;
logger::debug() << "iface::read() len=" << len;
return len;
}
@ -327,12 +353,16 @@ ssize_t iface::write(int fd, const address& daddr, const uint8_t* msg, size_t si
mhdr.msg_iov =& iov;
mhdr.msg_iovlen = 1;
logger::debug() << "iface::write() daddr=" << daddr.to_string() << ", len=" << size;
logger::debug() << "iface::write() ifa=" << name() << ", daddr=" << daddr.to_string() << ", len="
<< size;
int len;
if ((len = sendmsg(fd,& mhdr, 0)) < 0)
{
logger::error() << "iface::write() failed! error=" << logger::err() << ", ifa=" << name() << ", daddr=" << daddr.to_string();
return -1;
}
return len;
}
@ -343,20 +373,28 @@ ssize_t iface::read_solicit(address& saddr, address& daddr, address& taddr)
uint8_t msg[256];
ssize_t len;
if ((len = read(_pfd, (struct sockaddr* )&t_saddr, msg, sizeof(msg))) < 0)
if ((len = read(_pfd, (struct sockaddr*)&t_saddr, sizeof(struct sockaddr_ll), msg, sizeof(msg))) < 0) {
logger::warning() << "iface::read_solicit() failed: " << logger::err();
return -1;
}
struct ip6_hdr* ip6h =
(struct ip6_hdr* )(msg + ETH_HLEN);
struct nd_neighbor_solicit* ns =
(struct nd_neighbor_solicit* )(msg + ETH_HLEN + sizeof( struct ip6_hdr));
(struct nd_neighbor_solicit*)(msg + ETH_HLEN + sizeof(struct ip6_hdr));
taddr = ns->nd_ns_target;
daddr = ip6h->ip6_dst;
saddr = ip6h->ip6_src;
// Ignore packets sent from this machine
if (iface::is_local(saddr) == true) {
return 0;
}
logger::debug() << "iface::read_solicit() saddr=" << saddr.to_string() << ", daddr=" << daddr.to_string() << ", len=" << len;
logger::debug() << "iface::read_solicit() saddr=" << saddr.to_string()
<< ", daddr=" << daddr.to_string() << ", taddr=" << taddr.to_string() << ", len=" << len;
return len;
}
@ -380,7 +418,8 @@ ssize_t iface::write_solicit(const address& taddr)
memcpy(&ns->nd_ns_target,& taddr.const_addr(), sizeof(struct in6_addr));
memcpy(buf + sizeof(struct nd_neighbor_solicit) + sizeof(struct nd_opt_hdr),& hwaddr, 6);
memcpy(buf + sizeof(struct nd_neighbor_solicit) + sizeof(struct nd_opt_hdr),
&hwaddr, 6);
// FIXME: Alright, I'm lazy.
static address multicast("ff02::1:ff00:0000");
@ -393,9 +432,11 @@ ssize_t iface::write_solicit(const address& taddr)
daddr.addr().s6_addr[14] = taddr.const_addr().s6_addr[14];
daddr.addr().s6_addr[15] = taddr.const_addr().s6_addr[15];
logger::debug() << "iface::write_solicit() taddr=" << taddr.to_string() << ", daddr=" << daddr.to_string();
logger::debug() << "iface::write_solicit() taddr=" << taddr.to_string()
<< ", daddr=" << daddr.to_string();
return write(_ifd, daddr, (uint8_t* )buf, sizeof(struct nd_neighbor_solicit) + sizeof(struct nd_opt_hdr) + 6);
return write(_ifd, daddr, (uint8_t* )buf, sizeof(struct nd_neighbor_solicit)
+ sizeof(struct nd_opt_hdr) + 6);
}
ssize_t iface::write_advert(const address& daddr, const address& taddr, bool router)
@ -414,13 +455,15 @@ ssize_t iface::write_advert(const address& daddr, const address& taddr, bool rou
opt->nd_opt_len = 1;
na->nd_na_type = ND_NEIGHBOR_ADVERT;
na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED | (router ? ND_NA_FLAG_ROUTER : 0);
na->nd_na_flags_reserved = (daddr.is_multicast() ? 0 : ND_NA_FLAG_SOLICITED) | (router ? ND_NA_FLAG_ROUTER : 0);
memcpy(&na->nd_na_target,& taddr.const_addr(), sizeof(struct in6_addr));
memcpy(buf + sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr),& hwaddr, 6);
memcpy(buf + sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr),
&hwaddr, 6);
logger::debug() << "iface::write_advert() daddr=" << daddr.to_string() << ", taddr=" << taddr.to_string();
logger::debug() << "iface::write_advert() daddr=" << daddr.to_string()
<< ", taddr=" << taddr.to_string();
return write(_ifd, daddr, (uint8_t* )buf, sizeof(struct nd_neighbor_advert) +
sizeof(struct nd_opt_hdr) + 6);
@ -431,11 +474,22 @@ ssize_t iface::read_advert(address& saddr, address& taddr)
struct sockaddr_in6 t_saddr;
uint8_t msg[256];
ssize_t len;
memset(&t_saddr, 0, sizeof(struct sockaddr_in6));
t_saddr.sin6_family = AF_INET6;
t_saddr.sin6_port = htons(IPPROTO_ICMPV6); // Needed?
if ((len = read(_ifd, (struct sockaddr* )&t_saddr, msg, sizeof(msg))) < 0)
if ((len = read(_ifd, (struct sockaddr* )&t_saddr, sizeof(struct sockaddr_in6), msg, sizeof(msg))) < 0) {
logger::warning() << "iface::read_advert() failed: " << logger::err();
return -1;
}
saddr = t_saddr.sin6_addr;
// Ignore packets sent from this machine
if (iface::is_local(saddr) == true) {
return 0;
}
if (((struct icmp6_hdr* )msg)->icmp6_type != ND_NEIGHBOR_ADVERT)
return -1;
@ -447,6 +501,79 @@ ssize_t iface::read_advert(address& saddr, address& taddr)
return len;
}
bool iface::is_local(const address& addr)
{
// Check if the address is for an interface we own that is attached to
// one of the slave interfaces
for (std::list<ptr<route> >::iterator ad = address::addresses_begin(); ad != address::addresses_end(); ad++)
{
if ((*ad)->addr() == addr)
return true;
}
return false;
}
bool iface::handle_local(const address& saddr, const address& taddr)
{
// Check if the address is for an interface we own that is attached to
// one of the slave interfaces
for (std::list<ptr<route> >::iterator ad = address::addresses_begin(); ad != address::addresses_end(); ad++)
{
if ((*ad)->addr() == taddr)
{
// Loop through all the serves that are using this iface to respond to NDP solicitation requests
for (std::list<weak_ptr<proxy> >::iterator pit = serves_begin(); pit != serves_end(); pit++) {
ptr<proxy> pr = (*pit);
if (!pr) continue;
for (std::list<ptr<rule> >::iterator it = pr->rules_begin(); it != pr->rules_end(); it++) {
ptr<rule> ru = *it;
if (ru->daughter() && ru->daughter()->name() == (*ad)->ifname())
{
logger::debug() << "proxy::handle_solicit() found local taddr=" << taddr;
write_advert(saddr, taddr, false);
return true;
}
}
}
}
}
return false;
}
void iface::handle_reverse_advert(const address& saddr, const std::string& ifname)
{
if (!saddr.is_unicast())
return;
logger::debug()
<< "proxy::handle_reverse_advert()";
// Loop through all the parents that forward new NDP soliciation requests to this interface
for (std::list<weak_ptr<proxy> >::iterator pit = parents_begin(); pit != parents_end(); pit++) {
ptr<proxy> parent = (*pit);
if (!parent || !parent->ifa()) {
continue;
}
// Setup the reverse path on any proxies that are dealing
// with the reverse direction (this helps improve connectivity and
// latency in a full duplex setup)
for (std::list<ptr<rule> >::iterator it = parent->rules_begin(); it != parent->rules_end(); it++) {
ptr<rule> ru = *it;
if (ru->addr() == saddr &&
ru->daughter()->name() == ifname)
{
logger::debug() << " - generating artifical advertisement: " << ifname;
parent->handle_stateless_advert(saddr, saddr, ifname, ru->autovia());
}
}
}
}
void iface::fixup_pollfds()
{
_pollfds.resize(_map.size()* 2);
@ -469,22 +596,6 @@ void iface::fixup_pollfds()
}
}
void iface::remove_session(const ptr<session>& se)
{
for (std::list<weak_ptr<session> >::iterator it = _sessions.begin();
it != _sessions.end(); it++) {
if (*it == se) {
_sessions.erase(it);
break;
}
}
}
void iface::add_session(const ptr<session>& se)
{
_sessions.push_back(se);
}
void iface::cleanup()
{
for (std::map<std::string, weak_ptr<iface> >::iterator it = _map.begin();
@ -514,6 +625,7 @@ int iface::poll_all()
int len;
if ((len = ::poll(&_pollfds[0], _pollfds.size(), 50)) < 0) {
logger::error() << "Failed to poll interfaces: " << logger::err();
return -1;
}
@ -535,43 +647,107 @@ int iface::poll_all()
bool is_pfd = i++ % 2;
ptr<iface> ifa = i_it->second;
if (f_it->revents & POLLERR) {
logger::error() << "Error polling interface " << ifa->_name.c_str();
return -1;
}
if (!(f_it->revents & POLLIN)) {
continue;
}
ptr<iface> ifa = i_it->second;
address saddr, daddr, taddr;
ssize_t size;
if (is_pfd) {
if (ifa->read_solicit(saddr, daddr, taddr) < 0) {
size = ifa->read_solicit(saddr, daddr, taddr);
if (size < 0) {
logger::error() << "Failed to read from interface '%s'", ifa->_name.c_str();
continue;
}
if (!saddr.is_unicast() || !daddr.is_multicast()) {
}
if (size == 0) {
logger::debug() << "iface::read_solicit() loopback received and ignored";
continue;
}
if (ifa->_pr) {
ifa->_pr->handle_solicit(saddr, daddr, taddr);
// Process any local addresses for interfaces that we are proxying
if (ifa->handle_local(saddr, taddr) == true) {
continue;
}
// We have to handle all the parents who may be interested in
// the reverse path towards the one who sent this solicit.
// In fact, the parent need to know the source address in order
// to respond to NDP Solicitations
ifa->handle_reverse_advert(saddr, ifa->name());
// Loop through all the proxies that are using this iface to respond to NDP solicitation requests
bool handled = false;
for (std::list<weak_ptr<proxy> >::iterator pit = ifa->serves_begin(); pit != ifa->serves_end(); pit++) {
ptr<proxy> pr = (*pit);
if (!pr) continue;
// Process the solicitation request by relating it to other
// interfaces or lookup up any statics routes we have configured
handled = true;
pr->handle_solicit(saddr, taddr, ifa->name());
}
// If it was not handled then write an error message
if (handled == false) {
logger::debug() << " - solicit was ignored";
}
} else {
if (ifa->read_advert(saddr, taddr) < 0) {
size = ifa->read_advert(saddr, taddr);
if (size < 0) {
logger::error() << "Failed to read from interface '%s'", ifa->_name.c_str();
continue;
}
for (std::list<weak_ptr<session> >::iterator s_it = ifa->_sessions.begin();
s_it != ifa->_sessions.end(); s_it++) {
assert(!s_it->is_null());
const ptr<session> sess = *s_it;
if ((sess->taddr() == taddr) && (sess->status() == session::WAITING)) {
sess->handle_advert();
break;
if (size == 0) {
logger::debug() << "iface::read_advert() loopback received and ignored";
continue;
}
// Process the NDP advert
bool handled = false;
for (std::list<weak_ptr<proxy> >::iterator pit = ifa->parents_begin(); pit != ifa->parents_end(); pit++) {
ptr<proxy> pr = (*pit);
if (!pr || !pr->ifa()) {
continue;
}
// The proxy must have a rule for this interface or it is not meant to receive
// any notifications and thus they must be ignored
bool autovia = false;
bool is_relevant = false;
for (std::list<ptr<rule> >::iterator it = pr->rules_begin(); it != pr->rules_end(); it++) {
ptr<rule> ru = *it;
if (ru->addr() == taddr &&
ru->daughter() &&
ru->daughter()->name() == ifa->name())
{
is_relevant = true;
autovia = ru->autovia();
break;
}
}
if (is_relevant == false) {
logger::debug() << "iface::read_advert() advert is not for " << ifa->name() << "...skipping";
continue;
}
// Process the NDP advertisement
handled = true;
pr->handle_advert(saddr, taddr, ifa->name(), autovia);
}
// If it was not handled then write an error message
if (handled == false) {
logger::debug() << " - advert was ignored";
}
}
}
@ -583,7 +759,9 @@ int iface::allmulti(int state)
{
struct ifreq ifr;
logger::debug() << "iface::allmulti() state=" << state << ", _name=\"" << _name << "\"";
logger::debug()
<< "iface::allmulti() state="
<< state << ", _name=\"" << _name << "\"";
state = !!state;
@ -592,10 +770,11 @@ int iface::allmulti(int state)
strncpy(ifr.ifr_name, _name.c_str(), IFNAMSIZ);
if (ioctl(_pfd, SIOCGIFFLAGS, &ifr) < 0) {
logger::error() << "Failed to get allmulti: " << logger::err();
return -1;
}
int old_state = !!(ifr.ifr_flags & IFF_ALLMULTI);
int old_state = !!(ifr.ifr_flags &IFF_ALLMULTI);
if (state == old_state) {
return old_state;
@ -608,6 +787,46 @@ int iface::allmulti(int state)
}
if (ioctl(_pfd, SIOCSIFFLAGS, &ifr) < 0) {
logger::error() << "Failed to set allmulti: " << logger::err();
return -1;
}
return old_state;
}
int iface::promiscuous(int state)
{
struct ifreq ifr;
logger::debug()
<< "iface::promiscuous() state="
<< state << ", _name=\"" << _name << "\"";
state = !!state;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, _name.c_str(), IFNAMSIZ);
if (ioctl(_pfd, SIOCGIFFLAGS, &ifr) < 0) {
logger::error() << "Failed to get promiscuous: " << logger::err();
return -1;
}
int old_state = !!(ifr.ifr_flags &IFF_PROMISC);
if (state == old_state) {
return old_state;
}
if (state) {
ifr.ifr_flags |= IFF_PROMISC;
} else {
ifr.ifr_flags &= ~IFF_PROMISC;
}
if (ioctl(_pfd, SIOCSIFFLAGS, &ifr) < 0) {
logger::error() << "Failed to set promiscuous: " << logger::err();
return -1;
}
@ -619,14 +838,34 @@ const std::string& iface::name() const
return _name;
}
void iface::pr(const ptr<proxy>& pr)
void iface::add_serves(const ptr<proxy>& pr)
{
_pr = pr;
_serves.push_back(pr);
}
const ptr<proxy>& iface::pr() const
std::list<weak_ptr<proxy> >::iterator iface::serves_begin()
{
return _pr;
return _serves.begin();
}
std::list<weak_ptr<proxy> >::iterator iface::serves_end()
{
return _serves.end();
}
void iface::add_parent(const ptr<proxy>& pr)
{
_parents.push_back(pr);
}
std::list<weak_ptr<proxy> >::iterator iface::parents_begin()
{
return _parents.begin();
}
std::list<weak_ptr<proxy> >::iterator iface::parents_end()
{
return _parents.end();
}
NDPPD_NS_END

View File

@ -38,13 +38,13 @@ public:
static ptr<iface> open_ifd(const std::string& name);
static ptr<iface> open_pfd(const std::string& name);
static ptr<iface> open_pfd(const std::string& name, bool promiscuous);
static int poll_all();
static ssize_t read(int fd, struct sockaddr* saddr, uint8_t* msg, size_t size);
ssize_t read(int fd, struct sockaddr* saddr, ssize_t saddr_size, uint8_t* msg, size_t size);
static ssize_t write(int fd, const address& daddr, const uint8_t* msg, size_t size);
ssize_t write(int fd, const address& daddr, const uint8_t* msg, size_t size);
// Writes a NB_NEIGHBOR_SOLICIT message to the _ifd socket.
ssize_t write_solicit(const address& taddr);
@ -57,21 +57,31 @@ public:
// Reads a NB_NEIGHBOR_ADVERT message from the _ifd socket;
ssize_t read_advert(address& saddr, address& taddr);
bool handle_local(const address& saddr, const address& taddr);
bool is_local(const address& addr);
void handle_reverse_advert(const address& saddr, const std::string& ifname);
// Returns the name of the interface.
const std::string& name() const;
// Adds a session to be monitored for ND_NEIGHBOR_ADVERT messages.
void add_session(const ptr<session>& se);
void remove_session(const ptr<session>& se);
void pr(const ptr<proxy>& pr);
const ptr<proxy>& pr() const;
std::list<weak_ptr<proxy> >::iterator serves_begin();
std::list<weak_ptr<proxy> >::iterator serves_end();
void add_serves(const ptr<proxy>& proxy);
std::list<weak_ptr<proxy> >::iterator parents_begin();
std::list<weak_ptr<proxy> >::iterator parents_end();
void add_parent(const ptr<proxy>& parent);
static std::map<std::string, weak_ptr<iface> > _map;
private:
static std::map<std::string, weak_ptr<iface> > _map;
static bool _map_dirty;
@ -96,15 +106,16 @@ private:
// Previous state of ALLMULTI for the interface.
int _prev_allmulti;
// Previous state of PROMISC for the interface
int _prev_promiscuous;
// Name of this interface.
std::string _name;
// An array of sessions that are monitoring this interface for
// ND_NEIGHBOR_ADVERT messages.
std::list<weak_ptr<session> > _sessions;
weak_ptr<proxy> _pr;
std::list<weak_ptr<proxy> > _serves;
std::list<weak_ptr<proxy> > _parents;
// The link-layer address of this interface.
struct ether_addr hwaddr;
@ -112,6 +123,10 @@ private:
// Turns on/off ALLMULTI for this interface - returns the previous state
// or -1 if there was an error.
int allmulti(int state);
// Turns on/off PROMISC for this interface - returns the previous state
// or -1 if there was an error
int promiscuous(int state);
// Constructor.
iface();

View File

@ -18,6 +18,7 @@
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <iostream>
#include <sstream>
@ -79,6 +80,28 @@ std::string logger::format(const std::string& fmt, ...)
return buf;
}
// alpine has a broken definition for strerr_r
// see https://stackoverflow.com/questions/41953104/strerror-r-is-incorrectly-declared-on-alpine-linux
std::string logger::err_str(int result, char *buff, int err)
{
if (result)
sprintf(buff, "unknown error: %d", err);
return buff;
}
std::string logger::err_str(char *result, char *buff, int err)
{
return result;
}
std::string logger::err()
{
char buf[2048];
return logger::err_str(strerror_r(errno, buf, sizeof(buf)), buf, errno);
}
logger logger::error()
{
return logger(LOG_ERR);

View File

@ -71,7 +71,10 @@ public:
static logger debug();
static logger notice();
static std::string err();
private:
int _pri;
std::stringstream _ss;
@ -89,7 +92,9 @@ private:
static int _max_pri;
// fixes for strerr_r on alpine
static std::string err_str(int result, char *buff, int err);
static std::string err_str(char *result, char *buff, int err);
};
NDPPD_NS_END

275
src/nd-netlink.cc Normal file
View File

@ -0,0 +1,275 @@
//
//@file nd-netlink.cc
//
// Copyright 2016, Allied Telesis Labs New Zealand, Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <sys/socket.h>
#include <errno.h>
#include <netlink/route/addr.h>
#include <arpa/inet.h>
#include "ndppd.h"
#include <algorithm>
NDPPD_NS_BEGIN
pthread_mutex_t cs_mutex = PTHREAD_MUTEX_INITIALIZER;
struct in6_addr*
address_create_ipv6(struct in6_addr *local)
{
struct in6_addr *addr = (struct in6_addr *)calloc(1, sizeof(struct in6_addr));
memcpy(addr, local, sizeof(struct in6_addr));
return addr;
}
void if_add_to_list(int ifindex, const ptr<iface>& ifa)
{
bool found = false;
pthread_mutex_lock (&cs_mutex);
for (std::vector<interface>::iterator it = interfaces.begin();
it != interfaces.end(); it++) {
if ((*it).ifindex == ifindex) {
found = true;
break;
}
}
if (!found) {
logger::debug() << "rule::add_iface() if=" << ifa->name();
interface anInterface;
anInterface._name = ifa->name();
anInterface.ifindex = ifindex;
interfaces.push_back(anInterface);
}
pthread_mutex_unlock (&cs_mutex);
}
void
if_addr_add(int ifindex, struct in6_addr *iaddr)
{
pthread_mutex_lock (&cs_mutex);
for (std::vector<interface>::iterator it = interfaces.begin();
it != interfaces.end(); it++) {
if ((*it).ifindex == ifindex) {
address addr = address(*iaddr);
logger::debug() << "Adding addr " << addr.to_string();
std::list<address>::iterator it_addr;
it_addr = std::find((*it).addresses.begin(), (*it).addresses.end(), addr);
if (it_addr == (*it).addresses.end()) {
(*it).addresses.push_back(addr);
}
break;
}
}
free(iaddr);
pthread_mutex_unlock (&cs_mutex);
}
void
if_addr_del(int ifindex, struct in6_addr *iaddr)
{
pthread_mutex_lock (&cs_mutex);
for (std::vector<interface>::iterator it = interfaces.begin();
it != interfaces.end(); it++) {
if ((*it).ifindex == ifindex) {
address addr = address(*iaddr);
logger::debug() << "Deleting addr " << addr.to_string();
(*it).addresses.remove(addr);
break;
}
}
free(iaddr);
pthread_mutex_unlock (&cs_mutex);
}
bool
if_addr_find(std::string iface, const struct in6_addr *iaddr)
{
bool found = false;
pthread_mutex_lock (&cs_mutex);
for (std::vector<interface>::iterator it = interfaces.begin();
it != interfaces.end(); it++) {
if (iface.compare((*it)._name) == 0) {
address addr = address(*iaddr);
std::list<address>::iterator it_addr;
it_addr = std::find((*it).addresses.begin(), (*it).addresses.end(), addr);
if (it_addr != (*it).addresses.end()) {
found = true;
break;
}
}
}
pthread_mutex_unlock (&cs_mutex);
return found;
}
static void
nl_msg_newaddr(struct nlmsghdr *hdr)
{
struct ifaddrmsg *ifaddr =
(struct ifaddrmsg *)(((char *) hdr) + (sizeof(struct nlmsghdr)));
// parse the attributes
struct nlattr *attrs[IFA_MAX + 1];
struct nlattr *s = (struct nlattr *)(((char *) ifaddr) + (sizeof(struct ifaddrmsg)));
int len = nlmsg_datalen(hdr) - sizeof(struct ifinfomsg);
memset(&attrs, '\0', sizeof(attrs));
nla_parse(attrs, IFA_MAX, s, len, NULL);
struct in6_addr* addr = NULL;
if (ifaddr->ifa_family == AF_INET6) {
addr = address_create_ipv6((struct in6_addr *)nla_data(attrs[IFA_ADDRESS]));
if_addr_add(ifaddr->ifa_index, addr);
}
}
static void
nl_msg_deladdr(struct nlmsghdr *hdr)
{
struct ifaddrmsg *ifaddr =
(struct ifaddrmsg *)(((char *) hdr) + (sizeof(struct nlmsghdr)));
// parse the attributes
struct nlattr *attrs[IFA_MAX + 1];
struct nlattr *s = (struct nlattr *)(((char *) ifaddr) + (sizeof(struct ifaddrmsg)));
int len = nlmsg_datalen(hdr) - sizeof(struct ifinfomsg);
memset(&attrs, '\0', sizeof(attrs));
nla_parse(attrs, IFA_MAX, s, len, NULL);
struct in6_addr* addr = NULL;
if (ifaddr->ifa_family == AF_INET6) {
addr = address_create_ipv6((struct in6_addr *)nla_data(attrs[IFA_ADDRESS]));
if_addr_del(ifaddr->ifa_index, addr);
}
}
static void
new_addr(struct nl_object *obj, void *p)
{
struct rtnl_addr *addr = (struct rtnl_addr *) obj;
struct nl_addr *local = rtnl_addr_get_local(addr);
int family = rtnl_addr_get_family(addr);
int ifindex = rtnl_addr_get_ifindex(addr);
struct in6_addr* in_addr = NULL;
char ipstr[INET6_ADDRSTRLEN];
inet_ntop(family, nl_addr_get_binary_addr(local), ipstr, INET6_ADDRSTRLEN);
switch (family) {
case AF_INET:
break;
case AF_INET6:
in_addr = address_create_ipv6((struct in6_addr *)nl_addr_get_binary_addr(local));
if_addr_add(ifindex, in_addr);
break;
default:
logger::error() << "Unknown message family: " << family;
}
}
static int
nl_msg_handler(struct nl_msg *msg, void *arg)
{
logger::debug() << "nl_msg_handler";
struct nlmsghdr *hdr = nlmsg_hdr(msg);
switch (hdr->nlmsg_type) {
case RTM_NEWADDR:
nl_msg_newaddr(hdr);
break;
case RTM_DELADDR:
nl_msg_deladdr(hdr);
break;
default:
logger::error() << "Unknown message type: " << hdr->nlmsg_type;
}
return NL_OK;
}
static void *
netlink_monitor(void *p)
{
struct nl_sock *sock = (struct nl_sock *) p;
struct nl_cache *addr_cache;
// get all the current addresses
if (rtnl_addr_alloc_cache(sock, &addr_cache) < 0) {
perror("rtnl_addr_alloc_cache");
return NULL;
}
// add existing addresses
nl_cache_foreach(addr_cache, new_addr, NULL);
// destroy the cache
nl_cache_free(addr_cache);
// switch to notification mode
// disable sequence checking
nl_socket_disable_seq_check(sock);
// set the callback we want
nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, nl_msg_handler, NULL);
// subscribe to the IPv6 address change callbacks
nl_socket_add_memberships(sock, RTNLGRP_IPV6_IFADDR, 0);
while (1)
{
nl_recvmsgs_default(sock);
}
return NULL;
}
static pthread_t monitor_thread;
static struct nl_sock *monitor_sock;
struct nl_sock *control_sock;
bool
netlink_setup()
{
// create a netlink socket
control_sock = nl_socket_alloc();
nl_connect(control_sock, NETLINK_ROUTE);
// create a thread to run the netlink monitor in
// create a netlink socket
monitor_sock = nl_socket_alloc();
nl_connect(monitor_sock, NETLINK_ROUTE);
// increase the recv buffer size to capture all notifications
nl_socket_set_buffer_size(monitor_sock, 2048000, 0);
pthread_create(&monitor_thread, NULL, netlink_monitor, monitor_sock);
pthread_setname_np(monitor_thread, "netlink");
if (pthread_setschedprio(monitor_thread, -10) < 0)
{
logger::warning() << "setschedprio: " << strerror(errno);
}
return true;
}
bool
netlink_teardown()
{
void *res = 0;
pthread_cancel(monitor_thread);
pthread_join(monitor_thread, &res);
nl_socket_free(monitor_sock);
nl_socket_free(control_sock);
return true;
}
NDPPD_NS_END

28
src/nd-netlink.h Normal file
View File

@ -0,0 +1,28 @@
//
// @file nd-netlink.h
//
// Copyright 2016, Allied Telesis Labs New Zealand, Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
NDPPD_NS_BEGIN
bool netlink_teardown();
bool netlink_setup();
bool if_addr_find(std::string iface, const struct in6_addr *iaddr);
void if_add_to_list(int ifindex, const ptr<iface>& ifa);
NDPPD_NS_END

View File

@ -24,6 +24,7 @@
#include <getopt.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@ -32,20 +33,29 @@
using namespace ndppd;
int daemonize()
static int daemonize()
{
pid_t pid = fork();
if (pid < 0)
if (pid < 0) {
logger::error() << "Failed to fork during daemonize: " << logger::err();
return -1;
}
if (pid > 0)
exit(0);
pid_t sid = setsid();
umask(0);
if (sid < 0)
pid_t sid = setsid();
if (sid < 0) {
logger::error() << "Failed to setsid during daemonize: " << logger::err();
return -1;
}
if (chdir("/") < 0) {
logger::error() << "Failed to change path during daemonize: " << logger::err();
return -1;
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
@ -54,17 +64,12 @@ int daemonize()
return 0;
}
bool configure(const std::string& path)
static ptr<conf> load_config(const std::string& path)
{
ptr<conf> cf, x_cf;
if (!(cf = conf::load(path)))
return false;
if (!(x_cf = cf->find("route-ttl")))
route::ttl(30000);
else
route::ttl(*x_cf);
return (conf*)NULL;
std::vector<ptr<conf> >::const_iterator p_it;
@ -75,13 +80,94 @@ bool configure(const std::string& path)
if (pr_cf->empty()) {
logger::error() << "'proxy' section is missing interface name";
return false;
return (conf*)NULL;
}
ptr<proxy> pr = proxy::open(*pr_cf);
std::vector<ptr<conf> >::const_iterator r_it;
if (!pr) {
logger::error() << "Configuration failed for proxy '" << (const std::string& )*pr_cf << "'";
std::vector<ptr<conf> > rules(pr_cf->find_all("rule"));
for (r_it = rules.begin(); r_it != rules.end(); r_it++) {
ptr<conf> ru_cf =* r_it;
if (ru_cf->empty()) {
logger::error() << "'rule' is missing an IPv6 address/net";
return (conf*)NULL;
}
address addr(*ru_cf);
if (x_cf = ru_cf->find("iface")) {
if (ru_cf->find("static") || ru_cf->find("auto")) {
logger::error()
<< "Only one of 'iface', 'auto' and 'static' may "
<< "be specified.";
return (conf*)NULL;
}
if ((const std::string&)*x_cf == "") {
logger::error() << "'iface' expected an interface name";
return (conf*)NULL;
}
} else if (ru_cf->find("static")) {
if (ru_cf->find("auto")) {
logger::error()
<< "Only one of 'iface', 'auto' and 'static' may "
<< "be specified.";
return (conf*)NULL;
}
if (addr.prefix() <= 120) {
logger::warning()
<< "Low prefix length (" << addr.prefix()
<< " <= 120) when using 'static' method";
}
} else if (!ru_cf->find("auto")) {
logger::error()
<< "You must specify either 'iface', 'auto' or "
<< "'static'";
return (conf*)NULL;
}
}
}
return cf;
}
static bool configure(ptr<conf>& cf)
{
ptr<conf> x_cf;
if (!(x_cf = cf->find("route-ttl")))
route::ttl(30000);
else
route::ttl(*x_cf);
if (!(x_cf = cf->find("address-ttl")))
address::ttl(30000);
else
address::ttl(*x_cf);
std::list<ptr<rule> > myrules;
std::vector<ptr<conf> >::const_iterator p_it;
std::vector<ptr<conf> > proxies(cf->find_all("proxy"));
for (p_it = proxies.begin(); p_it != proxies.end(); p_it++) {
ptr<conf> pr_cf = *p_it;
if (pr_cf->empty()) {
return false;
}
bool promiscuous = false;
if (!(x_cf = pr_cf->find("promiscuous")))
promiscuous = false;
else
promiscuous = *x_cf;
ptr<proxy> pr = proxy::open(*pr_cf, promiscuous);
if (!pr || pr.is_null() == true) {
return false;
}
@ -89,11 +175,31 @@ bool configure(const std::string& path)
pr->router(true);
else
pr->router(*x_cf);
if (!(x_cf = pr_cf->find("autowire")))
pr->autowire(false);
else
pr->autowire(*x_cf);
if (!(x_cf = pr_cf->find("keepalive")))
pr->keepalive(true);
else
pr->keepalive(*x_cf);
if (!(x_cf = pr_cf->find("retries")))
pr->retries(3);
else
pr->retries(*x_cf);
if (!(x_cf = pr_cf->find("ttl")))
pr->ttl(30000);
else
pr->ttl(*x_cf);
if (!(x_cf = pr_cf->find("deadtime")))
pr->deadtime(pr->ttl());
else
pr->deadtime(*x_cf);
if (!(x_cf = pr_cf->find("timeout")))
pr->timeout(500);
@ -107,44 +213,78 @@ bool configure(const std::string& path)
for (r_it = rules.begin(); r_it != rules.end(); r_it++) {
ptr<conf> ru_cf =* r_it;
if (ru_cf->empty()) {
logger::error() << "'rule' is missing an IPv6 address/net";
return false;
}
address addr(*ru_cf);
bool autovia = false;
if (!(x_cf = ru_cf->find("autovia")))
autovia = false;
else
autovia = *x_cf;
if (x_cf = ru_cf->find("iface")) {
if ((const std::string& )*x_cf == "") {
logger::error() << "'iface' expected an interface name";
} else {
pr->add_rule(addr, iface::open_ifd(*x_cf));
if (x_cf = ru_cf->find("iface"))
{
ptr<iface> ifa = iface::open_ifd(*x_cf);
if (!ifa || ifa.is_null() == true) {
return false;
}
ifa->add_parent(pr);
myrules.push_back(pr->add_rule(addr, ifa, autovia));
} else if (ru_cf->find("auto")) {
pr->add_rule(addr, true);
myrules.push_back(pr->add_rule(addr, true));
} else {
if (!ru_cf->find("static")) {
logger::warning()
<< "## I'm going for 'static' since you didn't specify any method. Please fix this" << logger::endl
<< "## as it's not going to be supported in future versions of ndppd. (See 'man ndppd.conf')";
}
if (addr.prefix() <= 120) {
logger::warning()
<< "Low prefix length (" << addr.prefix() << " <= 120) when using 'static' method";
}
pr->add_rule(addr, false);
myrules.push_back(pr->add_rule(addr, false));
}
}
}
// Print out all the topology
for (std::map<std::string, weak_ptr<iface> >::iterator i_it = iface::_map.begin(); i_it != iface::_map.end(); i_it++) {
ptr<iface> ifa = i_it->second;
logger::debug() << "iface " << ifa->name() << " {";
for (std::list<weak_ptr<proxy> >::iterator pit = ifa->serves_begin(); pit != ifa->serves_end(); pit++) {
ptr<proxy> pr = (*pit);
if (!pr) continue;
logger::debug() << " " << "proxy " << logger::format("%x", pr.get_pointer()) << " {";
for (std::list<ptr<rule> >::iterator rit = pr->rules_begin(); rit != pr->rules_end(); rit++) {
ptr<rule> ru = *rit;
logger::debug() << " " << "rule " << logger::format("%x", ru.get_pointer()) << " {";
logger::debug() << " " << "taddr " << ru->addr()<< ";";
if (ru->is_auto())
logger::debug() << " " << "auto;";
else if (!ru->daughter())
logger::debug() << " " << "static;";
else
logger::debug() << " " << "iface " << ru->daughter()->name() << ";";
logger::debug() << " }";
}
logger::debug() << " }";
}
logger::debug() << " " << "parents {";
for (std::list<weak_ptr<proxy> >::iterator pit = ifa->parents_begin(); pit != ifa->parents_end(); pit++) {
ptr<proxy> pr = (*pit);
logger::debug() << " " << "parent " << logger::format("%x", pr.get_pointer()) << ";";
}
logger::debug() << " }";
logger::debug() << "}";
}
return true;
}
bool running = true;
static bool running = true;
void exit_ndppd(int sig)
static void exit_ndppd(int sig)
{
logger::error() << "Shutting down...";
running = 0;
@ -201,15 +341,26 @@ int main(int argc, char* argv[], char* env[])
}
}
logger::notice()
<< "ndppd (NDP Proxy Daemon) version " NDPPD_VERSION << logger::endl
<< "Using configuration file '" << config_path << "'";
// Load configuration.
ptr<conf> cf = load_config(config_path);
if (cf.is_null())
return -1;
if (daemon) {
logger::syslog(true);
if (daemonize() < 0) {
logger::error() << "Failed to daemonize process";
if (daemonize() < 0)
return 1;
}
}
if (!configure(cf))
return -1;
if (!pidfile.empty()) {
std::ofstream pf;
pf.open(pidfile.c_str(), std::ios::out | std::ios::trunc);
@ -217,23 +368,16 @@ int main(int argc, char* argv[], char* env[])
pf.close();
}
logger::notice()
<< "ndppd (NDP Proxy Daemon) version " NDPPD_VERSION << logger::endl
<< "Using configuration file '" << config_path << "'";
// Load configuration.
if (!configure(config_path))
return -1;
//route::load("/proc/net/ipv6_route");
// Time stuff.
struct timeval t1, t2;
gettimeofday(&t1, 0);
#ifdef WITH_ND_NETLINK
netlink_setup();
#endif
while (running) {
if (iface::poll_all() < 0) {
if (running) {
@ -252,10 +396,19 @@ int main(int argc, char* argv[], char* env[])
t1.tv_sec = t2.tv_sec;
t1.tv_usec = t2.tv_usec;
route::update(elapsed_time);
if (rule::any_auto())
route::update(elapsed_time);
if (rule::any_iface())
address::update(elapsed_time);
session::update_all(elapsed_time);
}
#ifdef WITH_ND_NETLINK
netlink_teardown();
#endif
logger::notice() << "Bye";
return 0;

View File

@ -21,7 +21,7 @@
#define NDPPD_NS_BEGIN namespace ndppd {
#define NDPPD_NS_END }
#define NDPPD_VERSION "0.2.3"
#define NDPPD_VERSION "0.2.4"
#include <assert.h>
@ -35,3 +35,4 @@
#include "proxy.h"
#include "session.h"
#include "rule.h"
#include "nd-netlink.h"

View File

@ -26,72 +26,88 @@
#include "session.h"
NDPPD_NS_BEGIN
static address all_nodes = address("ff02::1");
std::list<ptr<proxy> > proxy::_list;
proxy::proxy() :
_router(true), _ttl(30000), _timeout(500)
_router(true), _ttl(30000), _deadtime(3000), _timeout(500), _autowire(false), _keepalive(true), _promiscuous(false), _retries(3)
{
}
ptr<proxy> proxy::create(const ptr<iface>& ifa)
ptr<proxy> proxy::find_aunt(const std::string& ifname, const address& taddr)
{
for (std::list<ptr<proxy> >::iterator sit = _list.begin();
sit != _list.end(); sit++)
{
ptr<proxy> pr = (*sit);
bool has_addr = false;
for (std::list<ptr<rule> >::iterator it = pr->_rules.begin(); it != pr->_rules.end(); it++) {
ptr<rule> ru = *it;
if (ru->addr() == taddr) {
has_addr = true;
break;
}
}
if (has_addr == false) {
continue;
}
if (pr->ifa() && pr->ifa()->name() == ifname)
return pr;
}
return ptr<proxy>();
}
ptr<proxy> proxy::create(const ptr<iface>& ifa, bool promiscuous)
{
ptr<proxy> pr(new proxy());
pr->_ptr = pr;
pr->_ifa = ifa;
pr->_promiscuous = promiscuous;
_list.push_back(pr);
ifa->pr(pr);
ifa->add_serves(pr);
logger::debug() << "proxy::create() if=" << ifa->name();
return pr;
}
ptr<proxy> proxy::open(const std::string& ifname)
ptr<proxy> proxy::open(const std::string& ifname, bool promiscuous)
{
ptr<iface> ifa = iface::open_pfd(ifname);
ptr<iface> ifa = iface::open_pfd(ifname, promiscuous);
if (!ifa) {
return ptr<proxy>();
}
return create(ifa);
return create(ifa, promiscuous);
}
void proxy::handle_solicit(const address& saddr, const address& daddr,
const address& taddr)
ptr<session> proxy::find_or_create_session(const address& taddr)
{
logger::debug()
<< "proxy::handle_solicit() saddr=" << saddr.to_string()
<< ", taddr=" << taddr.to_string();
// Let's check this proxy's list of sessions to see if we can
// find one with the same target address.
for (std::list<ptr<session> >::iterator sit = _sessions.begin();
sit != _sessions.end(); sit++) {
if ((*sit)->taddr() == taddr) {
switch ((*sit)->status()) {
case session::WAITING:
case session::INVALID:
break;
case session::VALID:
(*sit)->send_advert();
}
return;
}
if ((*sit)->taddr() == taddr)
return (*sit);
}
ptr<session> se;
// Since we couldn't find a session that matched, we'll try to find
// a matching rule instead, and then set up a new session.
ptr<session> se;
for (std::list<ptr<rule> >::iterator it = _rules.begin();
it != _rules.end(); it++) {
ptr<rule> ru = *it;
@ -100,9 +116,9 @@ void proxy::handle_solicit(const address& saddr, const address& daddr,
if (ru->addr() == taddr) {
if (!se) {
se = session::create(_ptr, saddr, daddr, taddr);
se = session::create(_ptr, taddr, _autowire, _keepalive, _retries);
}
if (ru->is_auto()) {
ptr<route> rt = route::find(taddr);
@ -111,30 +127,100 @@ void proxy::handle_solicit(const address& saddr, const address& daddr,
} else {
ptr<iface> ifa = rt->ifa();
if (ifa && (ifa != ru->ifa())) {
if (ifa && (ifa != ru->daughter())) {
se->add_iface(ifa);
}
}
} else if (!ru->ifa()) {
} else if (!ru->daughter()) {
// This rule doesn't have an interface, and thus we'll consider
// it "static" and immediately send the response.
se->handle_advert();
return;
return se;
} else {
se->add_iface((*it)->ifa());
ptr<iface> ifa = ru->daughter();
se->add_iface(ifa);
#ifdef WITH_ND_NETLINK
if (if_addr_find(ifa->name(), &taddr.const_addr())) {
logger::debug() << "Sending NA out " << ifa->name();
se->add_iface(_ifa);
se->handle_advert();
}
#endif
}
}
}
if (se) {
_sessions.push_back(se);
se->send_solicit();
}
return se;
}
void proxy::handle_advert(const address& saddr, const address& taddr, const std::string& ifname, bool use_via)
{
// If a session exists then process the advert in the context of the session
for (std::list<ptr<session> >::iterator s_it = _sessions.begin();
s_it != _sessions.end(); s_it++)
{
const ptr<session> sess = *s_it;
if ((sess->taddr() == taddr)) {
sess->handle_advert(saddr, ifname, use_via);
}
}
}
ptr<rule> proxy::add_rule(const address& addr, const ptr<iface>& ifa)
void proxy::handle_stateless_advert(const address& saddr, const address& taddr, const std::string& ifname, bool use_via)
{
logger::debug()
<< "proxy::handle_stateless_advert() proxy=" << (ifa() ? ifa()->name() : "null") << ", taddr=" << taddr.to_string() << ", ifname=" << ifname;
ptr<session> se = find_or_create_session(taddr);
if (!se) return;
if (_autowire == true && se->status() == session::WAITING) {
se->handle_auto_wire(saddr, ifname, use_via);
}
}
void proxy::handle_solicit(const address& saddr, const address& taddr, const std::string& ifname)
{
logger::debug()
<< "proxy::handle_solicit()";
// Otherwise find or create a session to scan for this address
ptr<session> se = find_or_create_session(taddr);
if (!se) return;
// Touching the session will cause an NDP advert to be transmitted to all
// the daughters
se->touch();
// If our session is confirmed then we can respoond with an advert otherwise
// subscribe so that if it does become active we can notify everyone
if (saddr != taddr) {
switch (se->status()) {
case session::WAITING:
case session::INVALID:
se->add_pending(saddr);
break;
case session::VALID:
case session::RENEWING:
se->send_advert(saddr);
break;
}
}
}
ptr<rule> proxy::add_rule(const address& addr, const ptr<iface>& ifa, bool autovia)
{
ptr<rule> ru(rule::create(_ptr, addr, ifa));
ru->autovia(autovia);
_rules.push_back(ru);
return ru;
}
@ -146,6 +232,16 @@ ptr<rule> proxy::add_rule(const address& addr, bool aut)
return ru;
}
std::list<ptr<rule> >::iterator proxy::rules_begin()
{
return _rules.begin();
}
std::list<ptr<rule> >::iterator proxy::rules_end()
{
return _rules.end();
}
void proxy::remove_session(const ptr<session>& se)
{
_sessions.remove(se);
@ -156,6 +252,11 @@ const ptr<iface>& proxy::ifa() const
return _ifa;
}
bool proxy::promiscuous() const
{
return _promiscuous;
}
bool proxy::router() const
{
return _router;
@ -166,6 +267,36 @@ void proxy::router(bool val)
_router = val;
}
bool proxy::autowire() const
{
return _autowire;
}
void proxy::autowire(bool val)
{
_autowire = val;
}
int proxy::retries() const
{
return _retries;
}
void proxy::retries(int val)
{
_retries = val;
}
bool proxy::keepalive() const
{
return _keepalive;
}
void proxy::keepalive(bool val)
{
_keepalive = val;
}
int proxy::ttl() const
{
return _ttl;
@ -176,6 +307,16 @@ void proxy::ttl(int val)
_ttl = (val >= 0) ? val : 30000;
}
int proxy::deadtime() const
{
return _deadtime;
}
void proxy::deadtime(int val)
{
_deadtime = (val >= 0) ? val : 30000;
}
int proxy::timeout() const
{
return _timeout;

View File

@ -29,25 +29,50 @@ class iface;
class rule;
class proxy {
public:
static ptr<proxy> create(const ptr<iface>& ifa);
public:
static ptr<proxy> create(const ptr<iface>& ifa, bool promiscuous);
static ptr<proxy> find_aunt(const std::string& ifname, const address& taddr);
static ptr<proxy> open(const std::string& ifn);
void handle_solicit(const address& saddr, const address& daddr,
const address& taddr);
static ptr<proxy> open(const std::string& ifn, bool promiscuous);
ptr<session> find_or_create_session(const address& taddr);
void handle_advert(const address& saddr, const address& taddr, const std::string& ifname, bool use_via);
void handle_stateless_advert(const address& saddr, const address& taddr, const std::string& ifname, bool use_via);
void handle_solicit(const address& saddr, const address& taddr, const std::string& ifname);
void remove_session(const ptr<session>& se);
ptr<rule> add_rule(const address& addr, const ptr<iface>& ifa);
ptr<rule> add_rule(const address& addr, const ptr<iface>& ifa, bool autovia);
ptr<rule> add_rule(const address& addr, bool aut = false);
std::list<ptr<rule> >::iterator rules_begin();
std::list<ptr<rule> >::iterator rules_end();
const ptr<iface>& ifa() const;
bool promiscuous() const;
bool router() const;
void router(bool val);
bool autowire() const;
void autowire(bool val);
int retries() const;
void retries(int val);
bool keepalive() const;
void keepalive(bool val);
int timeout() const;
@ -56,6 +81,10 @@ public:
int ttl() const;
void ttl(int val);
int deadtime() const;
void deadtime(int val);
private:
static std::list<ptr<proxy> > _list;
@ -67,10 +96,18 @@ private:
std::list<ptr<rule> > _rules;
std::list<ptr<session> > _sessions;
bool _promiscuous;
bool _router;
bool _autowire;
int _retries;
bool _keepalive;
int _ttl, _timeout;
int _ttl, _deadtime, _timeout;
proxy();
};

View File

@ -70,6 +70,8 @@ protected:
void acquire(void* ptr)
{
if (!ptr) return;
_ref = new ptr_ref();
_ref->ptr = (T*)ptr;
_ref->wc = !!_weak;

View File

@ -81,6 +81,8 @@ std::string route::token(const char* str)
void route::load(const std::string& path)
{
// Hack to make sure the interfaces are not freed prematurely.
std::list<ptr<route> > tmp_routes(_routes);
_routes.clear();
logger::debug() << "reading routes";
@ -103,19 +105,19 @@ void route::load(const std::string& path)
unsigned char pfx;
if (hexdec(buf, (unsigned char* )&addr.addr(), 16) != 16) {
if (route::hexdec(buf, (unsigned char* )&addr.addr(), 16) != 16) {
// TODO: Warn here?
continue;
}
if (hexdec(buf + 33,& pfx, 1) != 1) {
if (route::hexdec(buf + 33,& pfx, 1) != 1) {
// TODO: Warn here?
continue;
}
addr.prefix((int)pfx);
route::create(addr, token(buf + 141));
route::create(addr, route::token(buf + 141));
}
} catch (std::ifstream::failure e) {
logger::warning() << "Failed to parse IPv6 routing data from '" << path << "'";

View File

@ -44,6 +44,12 @@ public:
const address& addr() const;
ptr<iface> ifa();
route(const address& addr, const std::string& ifname);
static size_t hexdec(const char* str, unsigned char* buf, size_t size);
static std::string token(const char* str);
private:
static int _ttl;
@ -56,14 +62,8 @@ private:
ptr<iface> _ifa;
static size_t hexdec(const char* str, unsigned char* buf, size_t size);
static std::string token(const char* str);
static std::list<ptr<route> > _routes;
route(const address& addr, const std::string& ifname);
};
NDPPD_NS_END

View File

@ -16,6 +16,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include "ndppd.h"
#include "rule.h"
@ -24,6 +25,14 @@
NDPPD_NS_BEGIN
std::vector<interface> interfaces;
bool rule::_any_aut = false;
bool rule::_any_iface = false;
bool rule::_any_static = false;
rule::rule()
{
}
@ -33,11 +42,22 @@ ptr<rule> rule::create(const ptr<proxy>& pr, const address& addr, const ptr<ifac
ptr<rule> ru(new rule());
ru->_ptr = ru;
ru->_pr = pr;
ru->_ifa = ifa;
ru->_daughter = ifa;
ru->_addr = addr;
ru->_aut = false;
_any_iface = true;
unsigned int ifindex;
logger::debug() << "rule::create() if=" << pr->ifa()->name() << ", addr=" << addr;
ifindex = if_nametoindex(pr->ifa()->name().c_str());
#ifdef WITH_ND_NETLINK
if_add_to_list(ifindex, pr->ifa());
#endif
ifindex = if_nametoindex(ifa->name().c_str());
#ifdef WITH_ND_NETLINK
if_add_to_list(ifindex, ifa);
#endif
logger::debug() << "rule::create() if=" << pr->ifa()->name() << ", slave=" << ifa->name() << ", addr=" << addr;
return ru;
}
@ -49,6 +69,10 @@ ptr<rule> rule::create(const ptr<proxy>& pr, const address& addr, bool aut)
ru->_pr = pr;
ru->_addr = addr;
ru->_aut = aut;
_any_aut = _any_aut || aut;
if (aut == false)
_any_static = true;
logger::debug()
<< "rule::create() if=" << pr->ifa()->name().c_str() << ", addr=" << addr
@ -62,9 +86,9 @@ const address& rule::addr() const
return _addr;
}
ptr<iface> rule::ifa() const
ptr<iface> rule::daughter() const
{
return _ifa;
return _daughter;
}
bool rule::is_auto() const
@ -72,6 +96,31 @@ bool rule::is_auto() const
return _aut;
}
bool rule::autovia() const
{
return _autovia;
}
void rule::autovia(bool val)
{
_autovia = val;
}
bool rule::any_auto()
{
return _any_aut;
}
bool rule::any_iface()
{
return _any_iface;
}
bool rule::any_static()
{
return _any_static;
}
bool rule::check(const address& addr) const
{
return _addr == addr;

View File

@ -18,6 +18,7 @@
#include <string>
#include <vector>
#include <map>
#include <list>
#include <sys/poll.h>
@ -36,24 +37,57 @@ public:
const address& addr() const;
ptr<iface> ifa() const;
ptr<iface> daughter() const;
bool is_auto() const;
bool check(const address& addr) const;
static bool any_auto();
static bool any_static();
static bool any_iface();
bool autovia() const;
void autovia(bool val);
private:
weak_ptr<rule> _ptr;
weak_ptr<proxy> _pr;
ptr<iface> _ifa;
ptr<iface> _daughter;
address _addr;
bool _aut;
static bool _any_aut;
static bool _any_static;
static bool _any_iface;
bool _autovia;
rule();
};
class interface {
public:
// List of IPv6 addresses on this interface
std::list<address> addresses;
// Index of this interface
int ifindex;
// Name of this interface.
std::string _name;
};
extern std::vector<interface> interfaces;
NDPPD_NS_END

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <algorithm>
#include <sstream>
#include "ndppd.h"
#include "proxy.h"
@ -24,6 +25,8 @@ NDPPD_NS_BEGIN
std::list<weak_ptr<session> > session::_sessions;
static address all_nodes = address("ff02::1");
void session::update_all(int elapsed_time)
{
for (std::list<weak_ptr<session> >::iterator it = _sessions.begin();
@ -40,10 +43,54 @@ void session::update_all(int elapsed_time)
}
switch (se->_status) {
case session::WAITING:
logger::debug() << "session is now invalid";
se->_status = session::INVALID;
se->_ttl = se->_pr->ttl();
if (se->_fails < se->_retries) {
logger::debug() << "session will keep trying [taddr=" << se->_taddr << "]";
se->_ttl = se->_pr->timeout();
se->_fails++;
// Send another solicit
se->send_solicit();
} else {
logger::debug() << "session is now invalid [taddr=" << se->_taddr << "]";
se->_status = session::INVALID;
se->_ttl = se->_pr->deadtime();
}
break;
case session::RENEWING:
logger::debug() << "session is became invalid [taddr=" << se->_taddr << "]";
if (se->_fails < se->_retries) {
se->_ttl = se->_pr->timeout();
se->_fails++;
// Send another solicit
se->send_solicit();
} else {
se->_pr->remove_session(se);
}
break;
case session::VALID:
if (se->touched() == true ||
se->keepalive() == true)
{
logger::debug() << "session is renewing [taddr=" << se->_taddr << "]";
se->_status = session::RENEWING;
se->_ttl = se->_pr->timeout();
se->_fails = 0;
se->_touched = false;
// Send another solicit to make sure the route is still valid
se->send_solicit();
} else {
se->_pr->remove_session(se);
}
break;
default:
@ -55,30 +102,34 @@ void session::update_all(int elapsed_time)
session::~session()
{
logger::debug() << "session::~session() this=" << logger::format("%x", this);
for (std::list<ptr<iface> >::iterator it = _ifaces.begin();
if (_wired == true) {
for (std::list<ptr<iface> >::iterator it = _ifaces.begin();
it != _ifaces.end(); it++) {
(*it)->remove_session(_ptr);
handle_auto_unwire((*it)->name());
}
}
}
ptr<session> session::create(const ptr<proxy>& pr, const address& saddr,
const address& daddr, const address& taddr)
ptr<session> session::create(const ptr<proxy>& pr, const address& taddr, bool auto_wire, bool keepalive, int retries)
{
ptr<session> se(new session());
se->_ptr = se;
se->_pr = pr;
se->_saddr = saddr;
se->_taddr = taddr;
se->_daddr = daddr;
se->_ttl = pr->timeout();
se->_ptr = se;
se->_pr = pr;
se->_taddr = taddr;
se->_autowire = auto_wire;
se->_keepalive = keepalive;
se->_retries = retries;
se->_wired = false;
se->_ttl = pr->ttl();
se->_touched = false;
_sessions.push_back(se);
logger::debug()
<< "session::create() pr=" << logger::format("%x", (proxy* )pr) << ", saddr=" << saddr
<< ", daddr=" << daddr << ", taddr=" << taddr << " =" << logger::format("%x", (session* )se);
<< "session::create() pr=" << logger::format("%x", (proxy* )pr) << ", proxy=" << ((pr->ifa()) ? pr->ifa()->name() : "null")
<< ", taddr=" << taddr << " =" << logger::format("%x", (session* )se);
return se;
}
@ -88,10 +139,19 @@ void session::add_iface(const ptr<iface>& ifa)
if (std::find(_ifaces.begin(), _ifaces.end(), ifa) != _ifaces.end())
return;
ifa->add_session(_ptr);
_ifaces.push_back(ifa);
}
void session::add_pending(const address& addr)
{
for (std::list<ptr<address> >::iterator ad = _pending.begin(); ad != _pending.end(); ad++) {
if (addr == (*ad))
return;
}
_pending.push_back(new address(addr));
}
void session::send_solicit()
{
logger::debug() << "session::send_solicit() (_ifaces.size() = " << _ifaces.size() << ")";
@ -103,17 +163,162 @@ void session::send_solicit()
}
}
void session::send_advert()
void session::touch()
{
_pr->ifa()->write_advert(_saddr, _taddr, _pr->router());
if (_touched == false)
{
_touched = true;
if (status() == session::WAITING || status() == session::INVALID) {
_ttl = _pr->timeout();
logger::debug() << "session is now probing [taddr=" << _taddr << "]";
send_solicit();
}
}
}
void session::send_advert(const address& daddr)
{
_pr->ifa()->write_advert(daddr, _taddr, _pr->router());
}
void session::handle_auto_wire(const address& saddr, const std::string& ifname, bool use_via)
{
if (_wired == true && (_wired_via.is_empty() || _wired_via == saddr))
return;
logger::debug()
<< "session::handle_auto_wire() taddr=" << _taddr << ", ifname=" << ifname;
if (use_via == true &&
_taddr != saddr &&
saddr.is_unicast() == true &&
saddr.is_multicast() == false)
{
std::stringstream route_cmd;
route_cmd << "ip";
route_cmd << " " << "-6";
route_cmd << " " << "route";
route_cmd << " " << "replace";
route_cmd << " " << std::string(saddr);
route_cmd << " " << "dev";
route_cmd << " " << ifname;
logger::debug()
<< "session::system(" << route_cmd.str() << ")";
system(route_cmd.str().c_str());
_wired_via = saddr;
}
else
_wired_via.reset();
{
std::stringstream route_cmd;
route_cmd << "ip";
route_cmd << " " << "-6";
route_cmd << " " << "route";
route_cmd << " " << "replace";
route_cmd << " " << std::string(_taddr);
if (_wired_via.is_empty() == false) {
route_cmd << " " << "via";
route_cmd << " " << std::string(_wired_via);
}
route_cmd << " " << "dev";
route_cmd << " " << ifname;
logger::debug()
<< "session::system(" << route_cmd.str() << ")";
system(route_cmd.str().c_str());
}
_wired = true;
}
void session::handle_auto_unwire(const std::string& ifname)
{
logger::debug()
<< "session::handle_auto_unwire() taddr=" << _taddr << ", ifname=" << ifname;
{
std::stringstream route_cmd;
route_cmd << "ip";
route_cmd << " " << "-6";
route_cmd << " " << "route";
route_cmd << " " << "flush";
route_cmd << " " << std::string(_taddr);
if (_wired_via.is_empty() == false) {
route_cmd << " " << "via";
route_cmd << " " << std::string(_wired_via);
}
route_cmd << " " << "dev";
route_cmd << " " << ifname;
logger::debug()
<< "session::system(" << route_cmd.str() << ")";
system(route_cmd.str().c_str());
}
if (_wired_via.is_empty() == false) {
std::stringstream route_cmd;
route_cmd << "ip";
route_cmd << " " << "-6";
route_cmd << " " << "route";
route_cmd << " " << "flush";
route_cmd << " " << std::string(_wired_via);
route_cmd << " " << "dev";
route_cmd << " " << ifname;
logger::debug()
<< "session::system(" << route_cmd.str() << ")";
system(route_cmd.str().c_str());
}
_wired = false;
_wired_via.reset();
}
void session::handle_advert(const address& saddr, const std::string& ifname, bool use_via)
{
if (_autowire == true && _status == WAITING) {
handle_auto_wire(saddr, ifname, use_via);
}
handle_advert();
}
void session::handle_advert()
{
_status = VALID;
logger::debug()
<< "session::handle_advert() taddr=" << _taddr << ", ttl=" << _pr->ttl();
if (_status != VALID) {
_status = VALID;
logger::debug() << "session is active [taddr=" << _taddr << "]";
}
_ttl = _pr->ttl();
_fails = 0;
if (!_pending.empty()) {
for (std::list<ptr<address> >::iterator ad = _pending.begin();
ad != _pending.end(); ad++) {
ptr<address> addr = (*ad);
logger::debug() << " - forward to " << addr;
send_advert();
send_advert(addr);
}
_pending.clear();
}
}
const address& session::taddr() const
@ -121,14 +326,34 @@ const address& session::taddr() const
return _taddr;
}
const address& session::saddr() const
bool session::autowire() const
{
return _saddr;
return _autowire;
}
const address& session::daddr() const
bool session::keepalive() const
{
return _daddr;
return _keepalive;
}
int session::retries() const
{
return _retries;
}
int session::fails() const
{
return _fails;
}
bool session::wired() const
{
return _wired;
}
bool session::touched() const
{
return _touched;
}
int session::status() const

View File

@ -16,6 +16,7 @@
#pragma once
#include <vector>
#include <string>
#include "ndppd.h"
@ -31,14 +32,30 @@ private:
weak_ptr<proxy> _pr;
address _saddr, _daddr, _taddr;
bool _autowire;
bool _keepalive;
bool _wired;
address _wired_via;
bool _touched;
// An array of interfaces this session is monitoring for
// ND_NEIGHBOR_ADVERT on.
std::list<ptr<iface> > _ifaces;
std::list<ptr<address> > _pending;
// The remaining time in miliseconds the object will stay in the
// interface's session array or cache.
int _ttl;
int _fails;
int _retries;
int _status;
@ -47,9 +64,10 @@ private:
public:
enum
{
WAITING, // Waiting for an advert response.
VALID, // Valid;
INVALID // Invalid;
WAITING, // Waiting for an advert response.
RENEWING, // Renewing;
VALID, // Valid;
INVALID // Invalid;
};
static void update_all(int elapsed_time);
@ -57,24 +75,45 @@ public:
// Destructor.
~session();
static ptr<session> create(const ptr<proxy>& pr, const address& saddr,
const address& daddr, const address& taddr);
static ptr<session> create(const ptr<proxy>& pr, const address& taddr, bool autowire, bool keepalive, int retries);
void add_iface(const ptr<iface>& ifa);
void add_pending(const address& addr);
const address& taddr() const;
const address& daddr() const;
const address& saddr() const;
bool autowire() const;
int retries() const;
int fails() const;
bool keepalive() const;
bool wired() const;
bool touched() const;
int status() const;
void status(int val);
void handle_advert();
void send_advert();
void handle_advert(const address& saddr, const std::string& ifname, bool use_via);
void handle_auto_wire(const address& saddr, const std::string& ifname, bool use_via);
void handle_auto_unwire(const std::string& ifname);
void touch();
void send_advert(const address& daddr);
void send_solicit();