Merge pull request #3394 from poettering/triple-tstamp

timestamping improvements and IPv6 RA revamp
This commit is contained in:
Lennart Poettering 2016-06-07 11:13:39 +02:00
commit a849538e3b
33 changed files with 2245 additions and 683 deletions

View File

@ -3430,6 +3430,9 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/network-internal.c \
src/libsystemd-network/network-internal.h \
src/libsystemd-network/sd-ndisc.c \
src/libsystemd-network/ndisc-internal.h \
src/libsystemd-network/ndisc-router.h \
src/libsystemd-network/ndisc-router.c \
src/libsystemd-network/icmp6-util.h \
src/libsystemd-network/icmp6-util.c \
src/libsystemd-network/sd-dhcp6-client.c \
@ -5462,6 +5465,7 @@ libnetworkd_core_la_SOURCES = \
src/network/networkd-ipv4ll.c \
src/network/networkd-dhcp4.c \
src/network/networkd-dhcp6.c \
src/network/networkd-ndisc.h \
src/network/networkd-ndisc.c \
src/network/networkd-network.h \
src/network/networkd-network.c \

View File

@ -528,23 +528,19 @@
</varlistentry>
<varlistentry>
<term><varname>IPv6AcceptRouterAdvertisements=</varname></term>
<listitem><para>Force the setting of the <filename>accept_ra</filename>
(router advertisements) setting for the interface.
When unset, the kernel default is used, and router
advertisements are accepted only when local forwarding
is disabled for that interface.
When router advertisements are accepted, they will
trigger the start of the DHCPv6 client if the relevant
flags are passed, or if no routers are found on the link.
Takes a boolean. If true, router advertisements are
accepted, when false, router advertisements are ignored,
independently of the local forwarding state.</para>
<listitem><para>Enable or disable IPv6 Router Advertisement (RA) reception support for the interface. Takes
a boolean parameter. If true, RAs are accepted; if false, RAs are ignored, independently of the local
forwarding state. When not set, the kernel default is used, and RAs are accepted only when local forwarding
is disabled for that interface. When RAs are accepted, they may trigger the start of the DHCPv6 client if
the relevant flags are set in the RA data, or if no routers are found on the link.</para>
<para>See
<ulink url="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt">ip-sysctl.txt</ulink>
in the kernel documentation, but note that systemd's
setting of <constant>1</constant> corresponds to
kernel's setting of <constant>2</constant>.</para>
<para>Further settings for the IPv6 RA support may be configured in the
<literal>[IPv6AcceptRouterAdvertisements]</literal> section, see below.</para>
<para>Also see <ulink
url="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt">ip-sysctl.txt</ulink> in the kernel
documentation regarding <literal>accept_ra</literal>, but note that systemd's setting of
<constant>1</constant> (i.e. true) corresponds to kernel's setting of <constant>2</constant>.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -799,7 +795,7 @@
false.</para>
<para>It is recommended to enable this option only on trusted networks, as setting this affects resolution
of all host names, in particular to single-label names. It is generally safer to use the supplied domain
of all host names, in particular of single-label names. It is generally safer to use the supplied domain
only as routing domain, rather than as search domain, in order to not have it affect local resolution of
single-label names.</para>
@ -898,6 +894,47 @@
</variablelist>
</refsect1>
<refsect1>
<title>[IPv6AcceptRouterAdvertisements] Section Options</title>
<para>The <literal>[IPv6AcceptRouterAdvertisements]</literal> section configures the IPv6 Router Advertisement
(RA) client, if it is enabled with the <varname>IPv6AcceptRouterAdvertisements=</varname> setting described
above:</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>UseDNS=</varname></term>
<listitem>
<para>When true (the default), the DNS servers received in the Router Advertisement will be used and take
precedence over any statically configured ones.</para>
<para>This corresponds to the <option>nameserver</option> option in <citerefentry
project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseDomains=</varname></term>
<listitem>
<para>Takes a boolean argument, or the special value <literal>route</literal>. When true, the domain name
received via IPv6 Router Advertisement (RA) will be used as DNS search domain over this link, similar to
the effect of the <option>Domains=</option> setting. If set to <literal>route</literal>, the domain name
received via IPv6 RA will be used for routing DNS queries only, but not for searching, similar to the
effect of the <option>Domains=</option> setting when the argument is prefixed with
<literal>~</literal>. Defaults to false.</para>
<para>It is recommended to enable this option only on trusted networks, as setting this affects resolution
of all host names, in particular of single-label names. It is generally safer to use the supplied domain
only as routing domain, rather than as search domain, in order to not have it affect local resolution of
single-label names.</para>
<para>When set to true, this setting corresponds to the <option>domain</option> option in <citerefentry
project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[DHCPServer] Section Options</title>
<para>The <literal>[DHCPServer]</literal> section contains

View File

@ -38,8 +38,7 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) {
return "FAILURE";
}
if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) {
if (IN_SET(level, EXIT_STATUS_SYSTEMD, EXIT_STATUS_LSB)) {
switch ((int) status) {
case EXIT_CHDIR:
@ -189,13 +188,9 @@ bool is_clean_exit(int code, int status, ExitStatusSet *success_status) {
/* If a daemon does not implement handlers for some of the
* signals that's not considered an unclean shutdown */
if (code == CLD_KILLED)
return
status == SIGHUP ||
status == SIGINT ||
status == SIGTERM ||
status == SIGPIPE ||
return IN_SET(status, SIGHUP, SIGINT, SIGTERM, SIGPIPE) ||
(success_status &&
set_contains(success_status->signal, INT_TO_PTR(status)));
set_contains(success_status->signal, INT_TO_PTR(status)));
return false;
}
@ -207,15 +202,14 @@ bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) {
return
code == CLD_EXITED &&
(status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED);
IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOTCONFIGURED);
}
void exit_status_set_free(ExitStatusSet *x) {
assert(x);
set_free(x->status);
set_free(x->signal);
x->status = x->signal = NULL;
x->status = set_free(x->status);
x->signal = set_free(x->signal);
}
bool exit_status_set_is_empty(ExitStatusSet *x) {

View File

@ -25,6 +25,12 @@
#include "macro.h"
#include "set.h"
/* This defines pretty names for the LSB 'start' verb exit codes. Note that they shouldn't be confused with the LSB
* 'status' verb exit codes which are defined very differently. For details see:
*
* https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
*/
typedef enum ExitStatus {
/* EXIT_SUCCESS defined by libc */
/* EXIT_FAILURE defined by libc */
@ -37,9 +43,7 @@ typedef enum ExitStatus {
/* The LSB suggests that error codes >= 200 are "reserved". We
* use them here under the assumption that they hence are
* unused by init scripts.
*
* http://refspecs.linuxfoundation.org/LSB_3.2.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html */
* unused by init scripts. */
EXIT_CHDIR = 200,
EXIT_NICE,
@ -81,9 +85,9 @@ typedef enum ExitStatus {
} ExitStatus;
typedef enum ExitStatusLevel {
EXIT_STATUS_MINIMAL,
EXIT_STATUS_SYSTEMD,
EXIT_STATUS_LSB,
EXIT_STATUS_MINIMAL, /* only cover libc EXIT_STATUS/EXIT_FAILURE */
EXIT_STATUS_SYSTEMD, /* cover libc and systemd's own exit codes */
EXIT_STATUS_LSB, /* cover libc, systemd's own and LSB exit codes */
EXIT_STATUS_FULL = EXIT_STATUS_LSB
} ExitStatusLevel;

View File

@ -28,18 +28,26 @@
#include "macro.h"
#include "util.h"
bool in4_addr_is_null(const struct in_addr *a) {
return a->s_addr == 0;
}
bool in6_addr_is_null(const struct in6_addr *a) {
return
a->s6_addr32[0] == 0 &&
a->s6_addr32[1] == 0 &&
a->s6_addr32[2] == 0 &&
a->s6_addr32[3] == 0;
}
int in_addr_is_null(int family, const union in_addr_union *u) {
assert(u);
if (family == AF_INET)
return u->in.s_addr == 0;
return in4_addr_is_null(&u->in);
if (family == AF_INET6)
return
u->in6.s6_addr32[0] == 0 &&
u->in6.s6_addr32[1] == 0 &&
u->in6.s6_addr32[2] == 0 &&
u->in6.s6_addr32[3] == 0;
return in6_addr_is_null(&u->in6);
return -EAFNOSUPPORT;
}

View File

@ -36,6 +36,9 @@ struct in_addr_data {
union in_addr_union address;
};
bool in4_addr_is_null(const struct in_addr *a);
bool in6_addr_is_null(const struct in6_addr *a);
int in_addr_is_null(int family, const union in_addr_union *u);
int in_addr_is_link_local(int family, const union in_addr_union *u);
int in_addr_is_localhost(int family, const union in_addr_union *u);

View File

@ -87,6 +87,16 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
return ts;
}
triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
assert(ts);
ts->realtime = now(CLOCK_REALTIME);
ts->monotonic = now(CLOCK_MONOTONIC);
ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
return ts;
}
dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
@ -104,6 +114,24 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
return ts;
}
triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
if (u == USEC_INFINITY || u <= 0) {
ts->realtime = ts->monotonic = ts->boottime = u;
return ts;
}
ts->realtime = u;
delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
return ts;
}
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
int64_t delta;
assert(ts);
@ -136,6 +164,26 @@ dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, us
return ts;
}
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
switch (clock) {
case CLOCK_REALTIME:
case CLOCK_REALTIME_ALARM:
return ts->realtime;
case CLOCK_MONOTONIC:
return ts->monotonic;
case CLOCK_BOOTTIME:
case CLOCK_BOOTTIME_ALARM:
return ts->boottime;
default:
return USEC_INFINITY;
}
}
usec_t timespec_load(const struct timespec *ts) {
assert(ts);
@ -1107,6 +1155,30 @@ clockid_t clock_boottime_or_monotonic(void) {
return CLOCK_MONOTONIC;
}
bool clock_supported(clockid_t clock) {
struct timespec ts;
switch (clock) {
case CLOCK_MONOTONIC:
case CLOCK_REALTIME:
return true;
case CLOCK_BOOTTIME:
return clock_boottime_supported();
case CLOCK_BOOTTIME_ALARM:
if (!clock_boottime_supported())
return false;
/* fall through, after checking the cached value for CLOCK_BOOTTIME. */
default:
/* For everything else, check properly */
return clock_gettime(clock, &ts) >= 0;
}
}
int get_timezone(char **tz) {
_cleanup_free_ char *t = NULL;
const char *e;

View File

@ -39,6 +39,12 @@ typedef struct dual_timestamp {
usec_t monotonic;
} dual_timestamp;
typedef struct triple_timestamp {
usec_t realtime;
usec_t monotonic;
usec_t boottime;
} triple_timestamp;
#define USEC_INFINITY ((usec_t) -1)
#define NSEC_INFINITY ((nsec_t) -1)
@ -69,7 +75,8 @@ typedef struct dual_timestamp {
#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1)
#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL })
#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {})
#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {})
usec_t now(clockid_t clock);
nsec_t now_nsec(clockid_t clock);
@ -79,11 +86,28 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u);
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u);
dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u);
triple_timestamp* triple_timestamp_get(triple_timestamp *ts);
triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u);
#define DUAL_TIMESTAMP_HAS_CLOCK(clock) \
IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC)
#define TRIPLE_TIMESTAMP_HAS_CLOCK(clock) \
IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM)
static inline bool dual_timestamp_is_set(dual_timestamp *ts) {
return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
(ts->monotonic > 0 && ts->monotonic != USEC_INFINITY));
}
static inline bool triple_timestamp_is_set(triple_timestamp *ts) {
return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) ||
(ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) ||
(ts->boottime > 0 && ts->boottime != USEC_INFINITY));
}
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock);
usec_t timespec_load(const struct timespec *ts) _pure_;
struct timespec *timespec_store(struct timespec *ts, usec_t u);
@ -113,6 +137,7 @@ int get_timezones(char ***l);
bool timezone_is_valid(const char *name);
bool clock_boottime_supported(void);
bool clock_supported(clockid_t clock);
clockid_t clock_boottime_or_monotonic(void);
#define xstrftime(buf, fmt, tm) \

View File

@ -109,3 +109,21 @@ static inline void unaligned_write_le64(void *_u, uint64_t a) {
unaligned_write_le32(u, (uint32_t) a);
unaligned_write_le32(u + 4, (uint32_t) (a >> 32));
}
#if __BYTE_ORDER == __BIG_ENDIAN
#define unaligned_read_ne16 unaligned_read_be16
#define unaligned_read_ne32 unaligned_read_be32
#define unaligned_read_ne64 unaligned_read_be64
#define unaligned_write_ne16 unaligned_write_be16
#define unaligned_write_ne32 unaligned_write_be32
#define unaligned_write_ne64 unaligned_write_be64
#else
#define unaligned_read_ne16 unaligned_read_le16
#define unaligned_read_ne32 unaligned_read_le32
#define unaligned_read_ne64 unaligned_read_le64
#define unaligned_write_ne16 unaligned_write_le16
#define unaligned_write_ne32 unaligned_write_le32
#define unaligned_write_ne64 unaligned_write_le64
#endif

View File

@ -49,7 +49,8 @@ int icmp6_bind_router_solicitation(int index) {
};
_cleanup_close_ int s = -1;
char ifname[IF_NAMESIZE] = "";
int r, zero = 0, one = 1, hops = 255;
static const int zero = 0, one = 1, hops = 255;
int r;
s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);
if (s < 0)
@ -85,6 +86,10 @@ int icmp6_bind_router_solicitation(int index) {
if (r < 0)
return -errno;
r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
if (r < 0)
return -errno;
if (if_indextoname(index, ifname) == 0)
return -errno;

View File

@ -28,6 +28,8 @@
#include "prioq.h"
struct sd_lldp {
unsigned n_ref;
int ifindex;
int fd;

View File

@ -360,9 +360,16 @@ end_marker:
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
assert(n);
if (n->ttl > 0)
n->until = usec_add(now(clock_boottime_or_monotonic()), n->ttl * USEC_PER_SEC);
else
if (n->ttl > 0) {
usec_t base;
/* Use the packet's timestamp if there is one known */
base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
if (base <= 0 || base == USEC_INFINITY)
base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
n->until = usec_add(base, n->ttl * USEC_PER_SEC);
} else
n->until = 0;
if (n->lldp)
@ -588,11 +595,11 @@ done:
return 0;
}
_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret) {
_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(ret_sec, -EINVAL);
*ret = n->ttl;
*ret_sec = n->ttl;
return 0;
}
@ -651,7 +658,7 @@ _public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint
return 0;
}
int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
int r;
@ -668,7 +675,7 @@ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t ra
return r;
*ret = n;
n = 0;
n = NULL;
return r;
}
@ -679,7 +686,7 @@ _public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
assert(n->raw_size >= sizeof(struct ether_header));
n->rindex = sizeof(struct ether_header);
return 0;
return n->rindex < n->raw_size;
}
_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
@ -693,7 +700,7 @@ _public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
if (n->rindex + 2 > n->raw_size) /* Truncated message */
return -EBADMSG;
length = LLDP_NEIGHBOR_LENGTH(n);
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
@ -711,7 +718,7 @@ _public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
*type = LLDP_NEIGHBOR_TYPE(n);
*type = LLDP_NEIGHBOR_TLV_TYPE(n);
return 0;
}
@ -743,14 +750,14 @@ _public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], u
if (r == 0)
return -ENXIO;
length = LLDP_NEIGHBOR_LENGTH(n);
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (length < 4)
return -EBADMSG;
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
d = LLDP_NEIGHBOR_DATA(n);
d = LLDP_NEIGHBOR_TLV_DATA(n);
memcpy(oui, d, 3);
*subtype = d[3];
@ -782,8 +789,7 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret,
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
length = LLDP_NEIGHBOR_LENGTH(n);
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
@ -792,3 +798,16 @@ _public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret,
return 0;
}
_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
assert_return(n, -EINVAL);
assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
assert_return(clock_supported(clock), -EOPNOTSUPP);
assert_return(ret, -EINVAL);
if (!triple_timestamp_is_set(&n->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&n->timestamp, clock);
return 0;
}

View File

@ -43,6 +43,8 @@ struct sd_lldp_neighbor {
sd_lldp *lldp;
unsigned n_ref;
triple_timestamp timestamp;
usec_t until;
unsigned prioq_idx;
@ -81,18 +83,18 @@ static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
}
static inline uint8_t LLDP_NEIGHBOR_TYPE(const sd_lldp_neighbor *n) {
static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
}
static inline size_t LLDP_NEIGHBOR_LENGTH(const sd_lldp_neighbor *n) {
static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
uint8_t *p;
p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
return p[1] + (((size_t) (p[0] & 1)) << 8);
}
static inline void* LLDP_NEIGHBOR_DATA(const sd_lldp_neighbor *n) {
static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
}

View File

@ -0,0 +1,49 @@
#pragma once
/***
This file is part of systemd.
Copyright (C) 2014 Intel Corporation. All rights reserved.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "log.h"
#include "sd-ndisc.h"
struct sd_ndisc {
unsigned n_ref;
int ifindex;
int fd;
sd_event *event;
int event_priority;
struct ether_addr mac_addr;
uint8_t hop_limit;
uint32_t mtu;
sd_event_source *recv_event_source;
sd_event_source *timeout_event_source;
unsigned nd_sent;
sd_ndisc_callback_t callback;
void *userdata;
};
#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__)
#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__)

View File

@ -0,0 +1,779 @@
/***
This file is part of systemd.
Copyright (C) 2014 Intel Corporation. All rights reserved.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <netinet/icmp6.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "hostname-util.h"
#include "missing.h"
#include "ndisc-internal.h"
#include "ndisc-router.h"
#include "strv.h"
_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) {
if (!rt)
return NULL;
assert(rt->n_ref > 0);
rt->n_ref++;
return rt;
}
_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) {
if (!rt)
return NULL;
assert(rt->n_ref > 0);
rt->n_ref--;
if (rt->n_ref > 0)
return NULL;
free(rt);
return NULL;
}
sd_ndisc_router *ndisc_router_new(size_t raw_size) {
sd_ndisc_router *rt;
rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
if (!rt)
return NULL;
rt->raw_size = raw_size;
rt->n_ref = 1;
return rt;
}
_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(raw || raw_size <= 0, -EINVAL);
rt = ndisc_router_new(raw_size);
if (!rt)
return -ENOMEM;
memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size);
r = ndisc_router_parse(rt);
if (r < 0)
return r;
*ret = rt;
rt = NULL;
return r;
}
_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
if (in6_addr_is_null(&rt->address))
return -ENODATA;
*ret_addr = rt->address;
return 0;
}
_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
assert_return(rt, -EINVAL);
assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
assert_return(clock_supported(clock), -EOPNOTSUPP);
assert_return(ret, -EINVAL);
if (!triple_timestamp_is_set(&rt->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&rt->timestamp, clock);
return 0;
}
_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
*ret = NDISC_ROUTER_RAW(rt);
*size = rt->raw_size;
return 0;
}
int ndisc_router_parse(sd_ndisc_router *rt) {
struct nd_router_advert *a;
const uint8_t *p;
bool has_mtu = false, has_flag_extension = false;
size_t left;
assert(rt);
if (rt->raw_size < sizeof(struct nd_router_advert)) {
log_ndisc("Too small to be a router advertisement, ignoring.");
return -EBADMSG;
}
/* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */
a = NDISC_ROUTER_RAW(rt);
if (a->nd_ra_type != ND_ROUTER_ADVERT) {
log_ndisc("Received ND packet that is not a router advertisement, ignoring.");
return -EBADMSG;
}
if (a->nd_ra_code != 0) {
log_ndisc("Received ND packet with wrong RA code, ignoring.");
return -EBADMSG;
}
rt->hop_limit = a->nd_ra_curhoplimit;
rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */
rt->lifetime = be16toh(a->nd_ra_router_lifetime);
rt->preference = (rt->flags >> 3) & 3;
if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
left = rt->raw_size - sizeof(struct nd_router_advert);
for (;;) {
uint8_t type;
size_t length;
if (left == 0)
break;
if (left < 2) {
log_ndisc("Option lacks header, ignoring datagram.");
return -EBADMSG;
}
type = p[0];
length = p[1] * 8;
if (length == 0) {
log_ndisc("Zero-length option, ignoring datagram.");
return -EBADMSG;
}
if (left < length) {
log_ndisc("Option truncated, ignoring datagram.");
return -EBADMSG;
}
switch (type) {
case SD_NDISC_OPTION_PREFIX_INFORMATION:
if (length != 4*8) {
log_ndisc("Prefix option of invalid size, ignoring datagram.");
return -EBADMSG;
}
if (p[2] > 128) {
log_ndisc("Bad prefix length, ignoring datagram.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_MTU: {
uint32_t m;
if (has_mtu) {
log_ndisc("MTU option specified twice, ignoring.");
continue;
}
if (length != 8) {
log_ndisc("MTU option of invalid size, ignoring datagram.");
return -EBADMSG;
}
m = be32toh(*(uint32_t*) (p + 4));
if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */
rt->mtu = m;
has_mtu = true;
break;
}
case SD_NDISC_OPTION_ROUTE_INFORMATION:
if (length < 1*8 || length > 3*8) {
log_ndisc("Route information option of invalid size, ignoring datagram.");
return -EBADMSG;
}
if (p[2] > 128) {
log_ndisc("Bad route prefix length, ignoring datagram.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_RDNSS:
if (length < 3*8 || (length % (2*8)) != 1*8) {
log_ndisc("RDNSS option has invalid size.");
return -EBADMSG;
}
break;
case SD_NDISC_OPTION_FLAGS_EXTENSION:
if (has_flag_extension) {
log_ndisc("Flags extension option specified twice, ignoring.");
continue;
}
if (length < 1*8) {
log_ndisc("Flags extension option has invalid size.");
return -EBADMSG;
}
/* Add in the additional flags bits */
rt->flags |=
((uint64_t) p[2] << 8) |
((uint64_t) p[3] << 16) |
((uint64_t) p[4] << 24) |
((uint64_t) p[5] << 32) |
((uint64_t) p[6] << 40) |
((uint64_t) p[7] << 48);
has_flag_extension = true;
break;
case SD_NDISC_OPTION_DNSSL:
if (length < 2*8) {
log_ndisc("DNSSL option has invalid size.");
return -EBADMSG;
}
break;
}
p += length, left -= length;
}
rt->rindex = sizeof(struct nd_router_advert);
return 0;
}
_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
*ret = rt->hop_limit;
return 0;
}
_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) {
assert_return(rt, -EINVAL);
assert_return(ret_flags, -EINVAL);
*ret_flags = rt->flags;
return 0;
}
_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) {
assert_return(rt, -EINVAL);
assert_return(ret_lifetime, -EINVAL);
*ret_lifetime = rt->lifetime;
return 0;
}
_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
*ret = rt->preference;
return 0;
}
_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (rt->mtu <= 0)
return -ENODATA;
*ret = rt->mtu;
return 0;
}
_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
assert_return(rt, -EINVAL);
assert(rt->raw_size >= sizeof(struct nd_router_advert));
rt->rindex = sizeof(struct nd_router_advert);
return rt->rindex < rt->raw_size;
}
_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
size_t length;
assert_return(rt, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
return -EBADMSG;
rt->rindex += length;
return rt->rindex < rt->raw_size;
}
_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
*ret = NDISC_ROUTER_OPTION_TYPE(rt);
return 0;
}
_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
uint8_t k;
int r;
assert_return(rt, -EINVAL);
r = sd_ndisc_router_option_get_type(rt, &k);
if (r < 0)
return r;
return type == k;
}
_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) {
size_t length;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
/* Note that this returns the full option, including the option header */
if (rt->rindex + 2 > rt->raw_size)
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
*size = length;
return 0;
}
static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) {
struct nd_opt_prefix_info *ri;
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length != sizeof(struct nd_opt_prefix_info))
return -EBADMSG;
ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex);
if (ri->nd_opt_pi_prefix_len > 128)
return -EBADMSG;
*ret = ri;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
struct nd_opt_prefix_info *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(ri->nd_opt_pi_valid_time);
return 0;
}
_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
*ret = be32toh(pi->nd_opt_pi_preferred_time);
return 0;
}
_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
*ret = pi->nd_opt_pi_flags_reserved;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
*ret_addr = pi->nd_opt_pi_prefix;
return 0;
}
_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
struct nd_opt_prefix_info *pi;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_prefix_info(rt, &pi);
if (r < 0)
return r;
if (pi->nd_opt_pi_prefix_len > 128)
return -EBADMSG;
*ret = pi->nd_opt_pi_prefix_len;
return 0;
}
static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) {
uint8_t *ri;
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 1*8 || length > 3*8)
return -EBADMSG;
ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
if (ri[2] > 128)
return -EBADMSG;
*ret = ri;
return 0;
}
_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(*(uint32_t*) (ri + 4));
return 0;
}
_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_addr, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
zero(*ret_addr);
memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8);
return 0;
}
_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = ri[2];
return 0;
}
_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_route_info(rt, &ri);
if (r < 0)
return r;
*ret = (ri[3] >> 3) & 3;
if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
*ret = SD_NDISC_PREFERENCE_MEDIUM;
return 0;
}
static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) {
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 3*8 || (length % (2*8)) != 1*8)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
return 0;
}
_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_rdnss_info(rt, &ri);
if (r < 0)
return r;
*ret = (const struct in6_addr*) (ri + 8);
return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16;
}
_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_rdnss_info(rt, &ri);
if (r < 0)
return r;
*ret = be32toh(*(uint32_t*) (ri + 4));
return 0;
}
static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) {
size_t length;
int r;
assert(rt);
assert(ret);
r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (length < 2*8)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
return 0;
}
_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *e = NULL;
size_t allocated = 0, n = 0, left;
uint8_t *ri, *p;
bool first = true;
int r;
unsigned k = 0;
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
r = get_dnssl_info(rt, &ri);
if (r < 0)
return r;
p = ri + 8;
left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8;
for (;;) {
if (left == 0) {
if (n > 0) /* Not properly NUL terminated */
return -EBADMSG;
break;
}
if (*p == 0) {
/* Found NUL termination */
if (n > 0) {
_cleanup_free_ char *normalized = NULL;
e[n] = 0;
r = dns_name_normalize(e, &normalized);
if (r < 0)
return r;
/* Ignore the root domain name or "localhost" and friends */
if (!is_localhost(normalized) &&
!dns_name_is_root(normalized)) {
if (strv_push(&l, normalized) < 0)
return -ENOMEM;
normalized = NULL;
k++;
}
}
n = 0;
first = true;
p++, left--;
continue;
}
/* Check for compression (which is not allowed) */
if (*p > 63)
return -EBADMSG;
if (1U + *p + 1U > left)
return -EBADMSG;
if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U))
return -ENOMEM;
if (first)
first = false;
else
e[n++] = '.';
r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
left -= 1 + *p;
p += 1 + *p;
}
if (strv_isempty(l)) {
*ret = NULL;
return 0;
}
*ret = l;
l = NULL;
return k;
}
_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) {
uint8_t *ri;
int r;
assert_return(rt, -EINVAL);
assert_return(ret_sec, -EINVAL);
r = get_dnssl_info(rt, &ri);
if (r < 0)
return r;
*ret_sec = be32toh(*(uint32_t*) (ri + 4));
return 0;
}

View File

@ -0,0 +1,62 @@
#pragma once
/***
This file is part of systemd.
Copyright (C) 2014 Intel Corporation. All rights reserved.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-ndisc.h"
#include "time-util.h"
struct sd_ndisc_router {
unsigned n_ref;
triple_timestamp timestamp;
struct in6_addr address;
/* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
size_t raw_size;
/* The current read index for the iterative option interface */
size_t rindex;
uint64_t flags;
unsigned preference;
uint16_t lifetime;
uint8_t hop_limit;
uint32_t mtu;
};
static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
}
static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex;
}
static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) {
return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0];
}
static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
}
sd_ndisc_router *ndisc_router_new(size_t raw_size);
int ndisc_router_parse(sd_ndisc_router *rt);

View File

@ -380,18 +380,21 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
return size;
}
void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
size_t size) {
void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size) {
unsigned i;
assert(f);
assert(addresses);
assert(size);
for (i = 0; i < size; i++)
fprintf(f, SD_NDISC_ADDRESS_FORMAT_STR"%s",
SD_NDISC_ADDRESS_FORMAT_VAL(addresses[i]),
(i < (size - 1)) ? " ": "");
for (i = 0; i < size; i++) {
char buffer[INET6_ADDRSTRLEN];
fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
if (i < size - 1)
fputc(' ', f);
}
}
int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {

View File

@ -43,7 +43,6 @@ static void lldp_flush_neighbors(sd_lldp *lldp) {
static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
assert(lldp);
assert(n);
log_lldp("Invoking callback for '%c'.", event);
@ -138,6 +137,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
if (lldp_neighbor_equal(n, old)) {
/* Is this equal, then restart the TTL counter, but don't do anyting else. */
old->timestamp = n->timestamp;
lldp_start_timer(lldp, old);
lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
return 0;
@ -171,7 +171,7 @@ static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
finish:
if (old)
lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
return r;
}
@ -202,6 +202,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
ssize_t space, length;
sd_lldp *lldp = userdata;
struct timespec ts;
assert(fd >= 0);
assert(lldp);
@ -215,21 +216,41 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
return -ENOMEM;
length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
if (length < 0)
if (length < 0) {
if (errno == EAGAIN || errno == EINTR)
return 0;
return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
}
if ((size_t) length != n->raw_size) {
log_lldp("Packet size mismatch.");
return -EINVAL;
}
/* Try to get the timestamp of this packet if it is known */
if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
else
triple_timestamp_get(&n->timestamp);
return lldp_handle_datagram(lldp, n);
}
static void lldp_reset(sd_lldp *lldp) {
assert(lldp);
lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
lldp->fd = safe_close(lldp->fd);
}
_public_ int sd_lldp_start(sd_lldp *lldp) {
int r;
assert_return(lldp, -EINVAL);
assert_return(lldp->event, -EINVAL);
assert_return(lldp->ifindex > 0, -EINVAL);
if (lldp->fd >= 0)
return 0;
@ -240,24 +261,21 @@ _public_ int sd_lldp_start(sd_lldp *lldp) {
if (lldp->fd < 0)
return lldp->fd;
if (lldp->event) {
r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
if (r < 0)
goto fail;
r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
if (r < 0)
goto fail;
r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
if (r < 0)
goto fail;
r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
if (r < 0)
goto fail;
(void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
}
(void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
log_lldp("Started LLDP client");
return 1;
fail:
lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
lldp->fd = safe_close(lldp->fd);
lldp_reset(lldp);
return r;
}
@ -267,10 +285,9 @@ _public_ int sd_lldp_stop(sd_lldp *lldp) {
if (lldp->fd < 0)
return 0;
lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
lldp->fd = safe_close(lldp->fd);
log_lldp("Stopping LLDP client");
lldp_reset(lldp);
lldp_flush_neighbors(lldp);
return 1;
@ -305,6 +322,12 @@ _public_ int sd_lldp_detach_event(sd_lldp *lldp) {
return 0;
}
_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
assert_return(lldp, NULL);
return lldp->event;
}
_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
assert_return(lldp, -EINVAL);
@ -314,39 +337,60 @@ _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *us
return 0;
}
_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
assert_return(lldp, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
assert_return(lldp->fd < 0, -EBUSY);
lldp->ifindex = ifindex;
return 0;
}
_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
if (!lldp)
return NULL;
assert(lldp->n_ref > 0);
lldp->n_ref++;
return lldp;
}
_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
if (!lldp)
return NULL;
assert(lldp->n_ref > 0);
lldp->n_ref --;
if (lldp->n_ref > 0)
return NULL;
lldp_reset(lldp);
sd_lldp_detach_event(lldp);
lldp_flush_neighbors(lldp);
hashmap_free(lldp->neighbor_by_id);
prioq_free(lldp->neighbor_by_expiry);
sd_event_source_unref(lldp->io_event_source);
sd_event_source_unref(lldp->timer_event_source);
sd_event_unref(lldp->event);
safe_close(lldp->fd);
free(lldp);
return NULL;
}
_public_ int sd_lldp_new(sd_lldp **ret, int ifindex) {
_public_ int sd_lldp_new(sd_lldp **ret) {
_cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
lldp = new0(sd_lldp, 1);
if (!lldp)
return -ENOMEM;
lldp->n_ref = 1;
lldp->fd = -1;
lldp->ifindex = ifindex;
lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
lldp->capability_mask = (uint16_t) -1;
@ -486,11 +530,10 @@ _public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *
/* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
* that our own can be filtered out here. */
if (!addr) {
if (addr)
lldp->filter_address = *addr;
else
zero(lldp->filter_address);
return 0;
}
lldp->filter_address = *addr;
return 0;
}

View File

@ -19,165 +19,71 @@
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <stdbool.h>
#include <string.h>
#include <sys/ioctl.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
#include "async.h"
#include "fd-util.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
#include "list.h"
#include "ndisc-internal.h"
#include "ndisc-router.h"
#include "socket-util.h"
#include "string-util.h"
#include "util.h"
#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
enum NDiscState {
NDISC_STATE_IDLE,
NDISC_STATE_SOLICITATION_SENT,
NDISC_STATE_ADVERTISEMENT_LISTEN,
_NDISC_STATE_MAX,
_NDISC_STATE_INVALID = -1,
};
static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
assert(ndisc);
#define IP6_MIN_MTU 1280U
#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr))
#define NDISC_OPT_LEN_UNITS 8U
log_ndisc("Invoking callback for '%c'.", event);
#define ND_RA_FLAG_PREF 0x18
#define ND_RA_FLAG_PREF_LOW 0x03
#define ND_RA_FLAG_PREF_MEDIUM 0x0
#define ND_RA_FLAG_PREF_HIGH 0x1
#define ND_RA_FLAG_PREF_INVALID 0x2
if (!ndisc->callback)
return;
typedef struct NDiscPrefix NDiscPrefix;
struct NDiscPrefix {
unsigned n_ref;
sd_ndisc *nd;
LIST_FIELDS(NDiscPrefix, prefixes);
uint8_t len;
usec_t valid_until;
struct in6_addr addr;
};
struct sd_ndisc {
unsigned n_ref;
enum NDiscState state;
int ifindex;
int fd;
sd_event *event;
int event_priority;
struct ether_addr mac_addr;
uint32_t mtu;
LIST_HEAD(NDiscPrefix, prefixes);
sd_event_source *recv_event_source;
sd_event_source *timeout_event_source;
unsigned nd_sent;
sd_ndisc_router_callback_t router_callback;
sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback;
sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback;
sd_ndisc_callback_t callback;
void *userdata;
};
#define log_ndisc_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__)
#define log_ndisc(p, fmt, ...) log_ndisc_errno(p, 0, fmt, ##__VA_ARGS__)
static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) {
if (!prefix)
return NULL;
assert(prefix->n_ref > 0);
prefix->n_ref--;
if (prefix->n_ref > 0)
return NULL;
if (prefix->nd)
LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix);
free(prefix);
return NULL;
ndisc->callback(ndisc, event, rt, ndisc->userdata);
}
static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) {
NDiscPrefix *prefix;
assert(ret);
prefix = new0(NDiscPrefix, 1);
if (!prefix)
return -ENOMEM;
prefix->n_ref = 1;
LIST_INIT(prefixes, prefix);
prefix->nd = nd;
*ret = prefix;
return 0;
}
int sd_ndisc_set_callback(
_public_ int sd_ndisc_set_callback(
sd_ndisc *nd,
sd_ndisc_router_callback_t router_callback,
sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback,
sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback,
sd_ndisc_callback_t callback,
void *userdata) {
assert_return(nd, -EINVAL);
nd->router_callback = router_callback;
nd->prefix_onlink_callback = prefix_onlink_callback;
nd->prefix_autonomous_callback = prefix_autonomous_callback;
nd->callback = callback;
nd->userdata = userdata;
return 0;
}
int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
assert_return(nd, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
assert_return(nd->fd < 0, -EBUSY);
nd->ifindex = ifindex;
return 0;
}
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
if (mac_addr)
memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr));
nd->mac_addr = *mac_addr;
else
zero(nd->mac_addr);
return 0;
}
int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
int r;
assert_return(nd, -EINVAL);
assert_return(nd->fd < 0, -EBUSY);
assert_return(!nd->event, -EBUSY);
if (event)
@ -193,21 +99,22 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
return 0;
}
int sd_ndisc_detach_event(sd_ndisc *nd) {
_public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
assert_return(nd->fd < 0, -EBUSY);
nd->event = sd_event_unref(nd->event);
return 0;
}
sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
assert_return(nd, NULL);
return nd->event;
}
sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
if (!nd)
return NULL;
@ -221,15 +128,14 @@ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) {
static int ndisc_reset(sd_ndisc *nd) {
assert(nd);
nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
nd->fd = asynchronous_close(nd->fd);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
nd->fd = safe_close(nd->fd);
return 0;
}
sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
NDiscPrefix *prefix, *p;
_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
if (!nd)
return NULL;
@ -242,16 +148,12 @@ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) {
ndisc_reset(nd);
sd_ndisc_detach_event(nd);
LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes)
prefix = ndisc_prefix_unref(prefix);
free(nd);
return NULL;
}
int sd_ndisc_new(sd_ndisc **ret) {
_public_ int sd_ndisc_new(sd_ndisc **ret) {
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
assert_return(ret, -EINVAL);
@ -261,223 +163,70 @@ int sd_ndisc_new(sd_ndisc **ret) {
return -ENOMEM;
nd->n_ref = 1;
nd->ifindex = -1;
nd->fd = -1;
LIST_HEAD_INIT(nd->prefixes);
*ret = nd;
nd = NULL;
return 0;
}
int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
assert_return(nd, -EINVAL);
assert_return(mtu, -EINVAL);
if (nd->mtu == 0)
return -ENOMSG;
return -ENODATA;
*mtu = nd->mtu;
return 0;
}
static int prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
const struct in6_addr *addr,
uint8_t addr_prefixlen) {
uint8_t bytes, mask, len;
_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
assert_return(nd, -EINVAL);
assert_return(ret, -EINVAL);
assert(prefix);
assert(addr);
len = MIN(prefixlen, addr_prefixlen);
bytes = len / 8;
mask = 0xff << (8 - len % 8);
if (memcmp(prefix, addr, bytes) != 0 ||
(prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
return -EADDRNOTAVAIL;
if (nd->hop_limit == 0)
return -ENODATA;
*ret = nd->hop_limit;
return 0;
}
static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr,
uint8_t addr_len, NDiscPrefix **result) {
NDiscPrefix *prefix, *p;
usec_t time_now;
static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
int r;
assert(nd);
assert(rt);
r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
r = ndisc_router_parse(rt);
if (r == -EBADMSG) /* Bad packet */
return 0;
if (r < 0)
return r;
LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
if (prefix->valid_until < time_now) {
prefix = ndisc_prefix_unref(prefix);
continue;
}
if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) {
*result = prefix;
return 0;
}
}
return -EADDRNOTAVAIL;
}
static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len,
const struct nd_opt_prefix_info *prefix_opt) {
NDiscPrefix *prefix;
uint32_t lifetime_valid, lifetime_preferred;
usec_t time_now;
char time_string[FORMAT_TIMESPAN_MAX];
int r;
assert(nd);
assert(prefix_opt);
if (len < prefix_opt->nd_opt_pi_len)
return -EBADMSG;
if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO)))
return 0;
if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0)
return 0;
/* Update global variables we keep */
if (rt->mtu > 0)
nd->mtu = rt->mtu;
if (rt->hop_limit > 0)
nd->hop_limit = rt->hop_limit;
lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time);
lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time);
if (lifetime_valid < lifetime_preferred)
return 0;
r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix,
prefix_opt->nd_opt_pi_prefix_len, &prefix);
if (r < 0) {
if (r != -EADDRNOTAVAIL)
return r;
/* if router advertisement prefix valid timeout is zero, the timeout
callback will be called immediately to clean up the prefix */
r = ndisc_prefix_new(nd, &prefix);
if (r < 0)
return r;
prefix->len = prefix_opt->nd_opt_pi_prefix_len;
memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
sizeof(prefix->addr));
log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
prefix->len, lifetime_valid,
format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
LIST_PREPEND(prefixes, nd->prefixes, prefix);
} else {
if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
uint8_t prefixlen;
prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
log_ndisc(nd, "Prefix length mismatch %d/%d using %d",
prefix->len,
prefix_opt->nd_opt_pi_prefix_len,
prefixlen);
prefix->len = prefixlen;
}
log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr),
prefix->len, lifetime_valid,
format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC));
}
r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
return r;
prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC;
if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback)
nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata);
if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback)
nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid,
nd->userdata);
log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
rt->lifetime);
ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
return 0;
}
static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, size_t len) {
struct nd_opt_hdr *opt_hdr;
void *opt;
assert(nd);
assert(ra);
if (len < sizeof(struct nd_router_advert) + NDISC_OPT_LEN_UNITS) {
log_ndisc(nd, "Router Advertisement below minimum length");
return -EBADMSG;
}
len -= sizeof(struct nd_router_advert);
opt = ra + 1;
opt_hdr = opt;
while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS) {
struct nd_opt_mtu *opt_mtu;
struct nd_opt_prefix_info *opt_prefix;
uint32_t mtu;
if (opt_hdr->nd_opt_len == 0)
return -EBADMSG;
switch (opt_hdr->nd_opt_type) {
case ND_OPT_MTU:
opt_mtu = opt;
mtu = be32toh(opt_mtu->nd_opt_mtu_mtu);
if (mtu != nd->mtu) {
nd->mtu = MAX(mtu, IP6_MIN_MTU);
log_ndisc(nd, "Router Advertisement link MTU %d using %d", mtu, nd->mtu);
}
break;
case ND_OPT_PREFIX_INFORMATION:
opt_prefix = opt;
ndisc_prefix_update(nd, len, opt_prefix);
break;
}
len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS;
opt = (void*) ((uint8_t*) opt + opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS);
opt_hdr = opt;
}
if (len > 0)
log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len);
return 0;
}
static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_free_ struct nd_router_advert *ra = NULL;
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
sd_ndisc *nd = userdata;
union {
struct cmsghdr cmsghdr;
uint8_t buf[CMSG_LEN(sizeof(int))];
uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
CMSG_SPACE(sizeof(struct timeval))];
} control = {};
struct iovec iov = {};
union sockaddr_union sa = {};
@ -490,10 +239,7 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
struct in6_addr *gw;
unsigned lifetime;
ssize_t len, buflen;
int r, pref, stateful;
assert(s);
assert(nd);
@ -501,35 +247,47 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
buflen = next_datagram_size_fd(fd);
if (buflen < 0)
return buflen;
return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
iov.iov_len = buflen;
ra = malloc(iov.iov_len);
if (!ra)
rt = ndisc_router_new(buflen);
if (!rt)
return -ENOMEM;
iov.iov_base = ra;
iov.iov_base = NDISC_ROUTER_RAW(rt);
iov.iov_len = rt->raw_size;
len = recvmsg(fd, &msg, 0);
len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0) {
if (errno == EAGAIN || errno == EINTR)
return 0;
return log_ndisc_errno(nd, errno, "Could not receive message from ICMPv6 socket: %m");
}
if ((size_t) len < sizeof(struct nd_router_advert)) {
log_ndisc(nd, "Too small to be a router advertisement: ignoring");
return 0;
return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
}
if (msg.msg_namelen == 0)
gw = NULL; /* only happens when running the test-suite over a socketpair */
else if (msg.msg_namelen != sizeof(sa.in6)) {
log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen);
return 0;
} else
gw = &sa.in6.sin6_addr;
if ((size_t) len != rt->raw_size) {
log_ndisc("Packet size mismatch.");
return -EINVAL;
}
if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
sa.in6.sin6_family == AF_INET6) {
if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
_cleanup_free_ char *addr = NULL;
(void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
return 0;
}
rt->address = sa.in6.sin6_addr;
} else if (msg.msg_namelen > 0) {
log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
return -EINVAL;
}
/* namelen == 0 only happens when running the test-suite over a socketpair */
assert(!(msg.msg_flags & MSG_CTRUNC));
assert(!(msg.msg_flags & MSG_TRUNC));
@ -538,61 +296,29 @@ static int ndisc_router_advertisement_recv(sd_event_source *s, int fd, uint32_t
if (cmsg->cmsg_level == SOL_IPV6 &&
cmsg->cmsg_type == IPV6_HOPLIMIT &&
cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
int hops = *(int*)CMSG_DATA(cmsg);
int hops = *(int*) CMSG_DATA(cmsg);
if (hops != 255) {
log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops);
log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
return 0;
}
break;
}
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SO_TIMESTAMP &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
triple_timestamp_from_realtime(&rt->timestamp, timeval_load(CMSG_DATA(cmsg)));
}
if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) {
_cleanup_free_ char *addr = NULL;
(void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr);
log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr));
return 0;
}
if (ra->nd_ra_type != ND_ROUTER_ADVERT)
return 0;
if (ra->nd_ra_code != 0)
return 0;
if (!triple_timestamp_is_set(&rt->timestamp))
triple_timestamp_get(&rt->timestamp);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER);
pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3;
if (!IN_SET(pref, ND_RA_FLAG_PREF_LOW, ND_RA_FLAG_PREF_HIGH))
pref = ND_RA_FLAG_PREF_MEDIUM;
lifetime = be16toh(ra->nd_ra_router_lifetime);
log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec",
stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none",
pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium",
lifetime);
r = ndisc_ra_parse(nd, ra, (size_t) len);
if (r < 0) {
log_ndisc_errno(nd, r, "Could not parse Router Advertisement: %m");
return 0;
}
if (nd->router_callback)
nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata);
return 0;
return ndisc_handle_datagram(nd, rt);
}
static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
sd_ndisc *nd = userdata;
usec_t time_now, next_timeout;
int r;
@ -601,43 +327,34 @@ static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec,
assert(nd);
assert(nd->event);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
if (nd->callback)
nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata);
nd->state = NDISC_STATE_ADVERTISEMENT_LISTEN;
} else {
r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
if (r < 0) {
log_ndisc_errno(nd, r, "Error sending Router Solicitation: %m");
goto fail;
} else {
nd->state = NDISC_STATE_SOLICITATION_SENT;
log_ndisc(nd, "Sent Router Solicitation");
}
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
return 0;
}
nd->nd_sent++;
r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
if (r < 0) {
log_ndisc_errno(r, "Error sending Router Solicitation: %m");
goto fail;
}
assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
log_ndisc("Sent Router Solicitation");
nd->nd_sent++;
next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(),
next_timeout, 0,
ndisc_router_solicitation_timeout, nd);
if (r < 0) {
log_ndisc_errno(nd, r, "Failed to allocate timer event: %m");
goto fail;
}
r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
if (r < 0) {
log_ndisc_errno(r, "Error updating timer: %m");
goto fail;
}
r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
if (r < 0) {
log_ndisc_errno(nd, r, "Cannot set timer priority: %m");
goto fail;
}
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
if (r < 0) {
log_ndisc_errno(r, "Error reenabling timer: %m");
goto fail;
}
return 0;
@ -647,38 +364,36 @@ fail:
return 0;
}
int sd_ndisc_stop(sd_ndisc *nd) {
_public_ int sd_ndisc_stop(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
if (nd->state == NDISC_STATE_IDLE)
if (nd->fd < 0)
return 0;
log_ndisc(nd, "Stopping IPv6 Router Solicitation client");
log_ndisc("Stopping IPv6 Router Solicitation client");
ndisc_reset(nd);
nd->state = NDISC_STATE_IDLE;
if (nd->callback)
nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata);
return 0;
return 1;
}
int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
_public_ int sd_ndisc_start(sd_ndisc *nd) {
int r;
assert_return(nd, -EINVAL);
assert_return(nd->event, -EINVAL);
assert_return(nd->ifindex > 0, -EINVAL);
assert_return(nd->state == NDISC_STATE_IDLE, -EBUSY);
r = icmp6_bind_router_solicitation(nd->ifindex);
if (r < 0)
return r;
if (nd->fd >= 0)
return 0;
nd->fd = r;
assert(!nd->recv_event_source);
assert(!nd->timeout_event_source);
r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_router_advertisement_recv, nd);
nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
if (nd->fd < 0)
return nd->fd;
r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
if (r < 0)
goto fail;
@ -688,7 +403,7 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_router_solicitation_timeout, nd);
r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd);
if (r < 0)
goto fail;
@ -698,8 +413,8 @@ int sd_ndisc_router_discovery_start(sd_ndisc *nd) {
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
log_ndisc(ns, "Started IPv6 Router Solicitation client");
return 0;
log_ndisc("Started IPv6 Router Solicitation client");
return 1;
fail:
ndisc_reset(nd);

View File

@ -54,11 +54,11 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n
static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
int r;
r = sd_lldp_new(lldp, 42);
r = sd_lldp_new(lldp);
if (r < 0)
return r;
r = sd_lldp_attach_event(*lldp, e, 0);
r = sd_lldp_set_ifindex(*lldp, 42);
if (r < 0)
return r;
@ -66,6 +66,10 @@ static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *
if (r < 0)
return r;
r = sd_lldp_attach_event(*lldp, e, 0);
if (r < 0)
return r;
r = sd_lldp_start(*lldp);
if (r < 0)
return r;

View File

@ -18,11 +18,15 @@
***/
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
#include "hexdecoct.h"
#include "icmp6-util.h"
#include "socket-util.h"
#include "strv.h"
static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@ -35,6 +39,144 @@ static int test_fd[2];
typedef int (*send_ra_t)(uint8_t flags);
static send_ra_t send_ra_function;
static void router_dump(sd_ndisc_router *rt) {
struct in6_addr addr;
char buf[FORMAT_TIMESTAMP_MAX];
uint8_t hop_limit;
uint64_t t, flags;
uint32_t mtu;
uint16_t lifetime;
unsigned preference;
int r;
assert_se(rt);
log_info("--");
assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA);
assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0);
log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t));
assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0);
log_info("Monotonic: %" PRIu64, t);
if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0)
log_info("No hop limit set");
else
log_info("Hop limit: %u", hop_limit);
assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
log_info("Flags: <%s|%s>",
flags & ND_RA_FLAG_OTHER ? "OTHER" : "",
flags & ND_RA_FLAG_MANAGED ? "MANAGED" : "");
assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0);
log_info("Preference: %s",
preference == SD_NDISC_PREFERENCE_LOW ? "low" :
preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium");
assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0);
log_info("Lifetime: %" PRIu16, lifetime);
if (sd_ndisc_router_get_mtu(rt, &mtu) < 0)
log_info("No MTU set");
else
log_info("MTU: %" PRIu32, mtu);
r = sd_ndisc_router_option_rewind(rt);
for (;;) {
uint8_t type;
assert_se(r >= 0);
if (r == 0)
break;
assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0);
log_info(">> Option %u", type);
switch (type) {
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
case SD_NDISC_OPTION_TARGET_LL_ADDRESS: {
_cleanup_free_ char *c = NULL;
const void *p;
size_t n;
assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0);
assert_se(n > 2);
assert_se(c = hexmem((uint8_t*) p + 2, n - 2));
log_info("Address: %s", c);
break;
}
case SD_NDISC_OPTION_PREFIX_INFORMATION: {
uint32_t lifetime_valid, lifetime_preferred;
unsigned prefix_len;
uint8_t pfl;
struct in6_addr a;
char buff[INET6_ADDRSTRLEN];
assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0);
log_info("Valid Lifetime: %" PRIu32, lifetime_valid);
assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0);
log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred);
assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0);
log_info("Flags: <%s|%s>",
pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "",
pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : "");
assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0);
log_info("Prefix Length: %u", prefix_len);
assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0);
log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff)));
break;
}
case SD_NDISC_OPTION_RDNSS: {
const struct in6_addr *a;
uint32_t lt;
int n, i;
n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
assert_se(n > 0);
for (i = 0; i < n; i++) {
char buff[INET6_ADDRSTRLEN];
log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff)));
}
assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, &lt) >= 0);
log_info("Lifetime: %" PRIu32, lt);
break;
}
case SD_NDISC_OPTION_DNSSL: {
_cleanup_strv_free_ char **l = NULL;
uint32_t lt;
int n, i;
n = sd_ndisc_router_dnssl_get_domains(rt, &l);
assert_se(n > 0);
for (i = 0; i < n; i++)
log_info("Domain: %s", l[i]);
assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lt) >= 0);
log_info("Lifetime: %" PRIu32, lt);
break;
}}
r = sd_ndisc_router_option_next(rt);
}
}
static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
void *userdata) {
assert_se(false);
@ -83,32 +225,39 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
return send_ra_function(0);
}
static void test_rs_done(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
sd_event *e = userdata;
static unsigned idx = 0;
uint8_t flags_array[] = {
uint64_t flags_array[] = {
0,
0,
0,
ND_RA_FLAG_OTHER,
ND_RA_FLAG_MANAGED
};
uint64_t flags;
uint32_t mtu;
assert_se(nd);
if (event != SD_NDISC_EVENT_ROUTER)
return;
router_dump(rt);
assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0);
assert_se(flags == flags_array[idx]);
idx++;
if (verbose)
printf(" got event 0x%02x\n", flags);
printf(" got event 0x%02" PRIx64 "\n", flags);
if (idx < ELEMENTSOF(flags_array)) {
send_ra(flags_array[idx]);
return;
}
assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENOMSG);
assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA);
sd_event_exit(e, 0);
}
@ -132,17 +281,17 @@ static void test_rs(void) {
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0);
assert_se(sd_ndisc_set_callback(nd, test_rs_done, NULL, NULL, NULL, e) >= 0);
assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0);
assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(),
time_now + 2 *USEC_PER_SEC, 0,
test_rs_hangcheck, NULL) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
assert_se(sd_ndisc_stop(nd) >= 0);
assert_se(sd_ndisc_router_discovery_start(nd) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
sd_event_loop(e);

View File

@ -216,8 +216,7 @@ struct sd_event {
pid_t original_pid;
unsigned iteration;
dual_timestamp timestamp;
usec_t timestamp_boottime;
triple_timestamp timestamp;
int state;
bool exit_requested:1;
@ -1072,16 +1071,16 @@ _public_ int sd_event_add_time(
assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
assert_return(!event_pid_changed(e), -ECHILD);
if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) &&
!clock_boottime_supported())
if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
return -EOPNOTSUPP;
type = clock_to_event_source_type(clock); /* checks whether sd-event supports this clock */
if (type < 0)
return -EOPNOTSUPP;
if (!callback)
callback = time_exit_callback;
type = clock_to_event_source_type(clock);
assert_return(type >= 0, -EOPNOTSUPP);
d = event_get_clock_data(e, type);
assert(d);
@ -2530,9 +2529,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
goto finish;
}
dual_timestamp_get(&e->timestamp);
if (clock_boottime_supported())
e->timestamp_boottime = now(CLOCK_BOOTTIME);
triple_timestamp_get(&e->timestamp);
for (i = 0; i < m; i++) {
@ -2573,7 +2570,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0)
goto finish;
r = process_timer(e, e->timestamp_boottime, &e->boottime);
r = process_timer(e, e->timestamp.boottime, &e->boottime);
if (r < 0)
goto finish;
@ -2585,7 +2582,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
if (r < 0)
goto finish;
r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm);
r = process_timer(e, e->timestamp.boottime, &e->boottime_alarm);
if (r < 0)
goto finish;
@ -2759,43 +2756,24 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
assert_return(e, -EINVAL);
assert_return(usec, -EINVAL);
assert_return(!event_pid_changed(e), -ECHILD);
assert_return(IN_SET(clock,
CLOCK_REALTIME,
CLOCK_REALTIME_ALARM,
CLOCK_MONOTONIC,
CLOCK_BOOTTIME,
CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP);
if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
return -EOPNOTSUPP;
/* Generate a clean error in case CLOCK_BOOTTIME is not available. Note that don't use clock_supported() here,
* for a reason: there are systems where CLOCK_BOOTTIME is supported, but CLOCK_BOOTTIME_ALARM is not, but for
* the purpose of getting the time this doesn't matter. */
if (IN_SET(clock, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) && !clock_boottime_supported())
return -EOPNOTSUPP;
if (!dual_timestamp_is_set(&e->timestamp)) {
if (!triple_timestamp_is_set(&e->timestamp)) {
/* Implicitly fall back to now() if we never ran
* before and thus have no cached time. */
*usec = now(clock);
return 1;
}
switch (clock) {
case CLOCK_REALTIME:
case CLOCK_REALTIME_ALARM:
*usec = e->timestamp.realtime;
break;
case CLOCK_MONOTONIC:
*usec = e->timestamp.monotonic;
break;
case CLOCK_BOOTTIME:
case CLOCK_BOOTTIME_ALARM:
*usec = e->timestamp_boottime;
break;
default:
assert_not_reached("Unknown clock?");
}
*usec = triple_timestamp_by_clock(&e->timestamp, clock);
return 0;
}

View File

@ -60,10 +60,15 @@ static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m,
return 1;
}
static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
uint32_t lifetime_preferred, uint32_t lifetime_valid) {
int r;
static int dhcp6_address_change(
Link *link,
struct in6_addr *ip6_addr,
uint32_t lifetime_preferred,
uint32_t lifetime_valid) {
_cleanup_address_free_ Address *addr = NULL;
char buffer[INET6_ADDRSTRLEN];
int r;
r = address_new(&addr);
if (r < 0)
@ -79,8 +84,8 @@ static int dhcp6_address_change(Link *link, struct in6_addr *ip6_addr,
addr->cinfo.ifa_valid = lifetime_valid;
log_link_info(link,
"DHCPv6 address "SD_NDISC_ADDRESS_FORMAT_STR"/%d timeout preferred %d valid %d",
SD_NDISC_ADDRESS_FORMAT_VAL(addr->in_addr.in6),
"DHCPv6 address %s/%d timeout preferred %d valid %d",
inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
addr->prefixlen, lifetime_preferred, lifetime_valid);
r = address_configure(addr, link, dhcp6_address_handler, true);

View File

@ -28,8 +28,9 @@
#include "fileio.h"
#include "netlink-util.h"
#include "network-internal.h"
#include "networkd.h"
#include "networkd-lldp-tx.h"
#include "networkd-ndisc.h"
#include "networkd.h"
#include "set.h"
#include "socket-util.h"
#include "stdio-util.h"
@ -504,7 +505,10 @@ static void link_free(Link *link) {
sd_ipv4ll_unref(link->ipv4ll);
sd_dhcp6_client_unref(link->dhcp6_client);
sd_ndisc_unref(link->ndisc_router_discovery);
sd_ndisc_unref(link->ndisc);
set_free_free(link->ndisc_rdnss);
set_free_free(link->ndisc_dnssl);
if (link->manager)
hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
@ -616,8 +620,8 @@ static int link_stop_clients(Link *link) {
r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m");
}
if (link->ndisc_router_discovery) {
k = sd_ndisc_stop(link->ndisc_router_discovery);
if (link->ndisc) {
k = sd_ndisc_stop(link->ndisc);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m");
}
@ -1453,11 +1457,11 @@ static int link_acquire_ipv6_conf(Link *link) {
}
if (link_ipv6_accept_ra_enabled(link)) {
assert(link->ndisc_router_discovery);
assert(link->ndisc);
log_link_debug(link, "Discovering IPv6 routers");
r = sd_ndisc_router_discovery_start(link->ndisc_router_discovery);
r = sd_ndisc_start(link->ndisc);
if (r < 0 && r != -EBUSY)
return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
}
@ -2364,7 +2368,11 @@ static int link_configure(Link *link) {
}
if (link_lldp_rx_enabled(link)) {
r = sd_lldp_new(&link->lldp, link->ifindex);
r = sd_lldp_new(&link->lldp);
if (r < 0)
return r;
r = sd_lldp_set_ifindex(link->lldp, link->ifindex);
if (r < 0)
return r;
@ -3083,6 +3091,22 @@ int link_save(Link *link) {
if (space)
fputc(' ', f);
serialize_in6_addrs(f, in6_addrs, r);
space = true;
}
}
/* Make sure to flush out old entries before we use the NDISC data */
ndisc_vacuum(link);
if (link->network->dhcp_use_dns && link->ndisc_rdnss) {
NDiscRDNSS *dd;
SET_FOREACH(dd, link->ndisc_rdnss, i) {
if (space)
fputc(' ', f);
serialize_in6_addrs(f, &dd->address, 1);
space = true;
}
}
@ -3128,7 +3152,6 @@ int link_save(Link *link) {
if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
if (link->dhcp_lease)
(void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
if (dhcp6_lease)
(void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
}
@ -3136,22 +3159,34 @@ int link_save(Link *link) {
fputs("DOMAINS=", f);
fputstrv(f, link->network->search_domains, NULL, &space);
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp_domainname)
fputs_with_space(f, dhcp_domainname, NULL, &space);
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
NDiscDNSSL *dd;
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp6_domains)
fputstrv(f, dhcp6_domains, NULL, &space);
if (dhcp_domainname)
fputs_with_space(f, dhcp_domainname, NULL, &space);
if (dhcp6_domains)
fputstrv(f, dhcp6_domains, NULL, &space);
SET_FOREACH(dd, link->ndisc_dnssl, i)
fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
}
fputc('\n', f);
fputs("ROUTE_DOMAINS=", f);
fputstrv(f, link->network->route_domains, NULL, NULL);
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp_domainname)
fputs_with_space(f, dhcp_domainname, NULL, &space);
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
NDiscDNSSL *dd;
if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp6_domains)
fputstrv(f, dhcp6_domains, NULL, &space);
if (dhcp_domainname)
fputs_with_space(f, dhcp_domainname, NULL, &space);
if (dhcp6_domains)
fputstrv(f, dhcp6_domains, NULL, &space);
SET_FOREACH(dd, link->ndisc_dnssl, i)
fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
}
fputc('\n', f);

View File

@ -98,6 +98,7 @@ typedef struct Link {
unsigned dhcp4_messages;
bool dhcp4_configured;
bool dhcp6_configured;
unsigned ndisc_messages;
bool ndisc_configured;
@ -111,7 +112,10 @@ typedef struct Link {
sd_dhcp_server *dhcp_server;
sd_ndisc *ndisc_router_discovery;
sd_ndisc *ndisc;
Set *ndisc_rdnss;
Set *ndisc_dnssl;
sd_dhcp6_client *dhcp6_client;
bool rtnl_extended_attrs;
@ -161,7 +165,6 @@ int ipv4ll_configure(Link *link);
int dhcp4_configure(Link *link);
int dhcp6_configure(Link *link);
int dhcp6_request_address(Link *link, int ir);
int ndisc_configure(Link *link);
const char* link_state_to_string(LinkState s) _const_;
LinkState link_state_from_string(const char *s) _pure_;

View File

@ -17,14 +17,15 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <netinet/ether.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <linux/if.h>
#include "sd-ndisc.h"
#include "networkd.h"
#include "networkd-ndisc.h"
#define NDISC_DNSSL_MAX 64U
#define NDISC_RDNSS_MAX 64U
static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
_cleanup_link_unref_ Link *link = userdata;
@ -49,19 +50,92 @@ static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *
return 1;
}
static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
unsigned lifetime_preferred, unsigned lifetime_valid, void *userdata) {
_cleanup_address_free_ Address *address = NULL;
Link *link = userdata;
static void ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
_cleanup_route_free_ Route *route = NULL;
struct in6_addr gateway;
uint16_t lifetime;
unsigned preference;
usec_t time_now;
int r;
assert(nd);
assert(link);
assert(link->network);
assert(rt);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
r = sd_ndisc_router_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
if (lifetime == 0) /* not a default router */
return;
r = sd_ndisc_router_get_address(rt, &gateway);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
r = sd_ndisc_router_get_preference(rt, &preference);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
return;
}
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = route_new(&route);
if (r < 0) {
log_link_error_errno(link, r, "Could not allocate route: %m");
return;
}
route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->pref = preference;
route->gw.in6 = gateway;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set default route: %m");
link_enter_failed(link);
return;
}
link->ndisc_messages++;
}
static void ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_address_free_ Address *address = NULL;
uint32_t lifetime_valid, lifetime_preferred;
unsigned prefixlen;
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix length: %m");
return;
}
r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m");
return;
}
r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m");
return;
}
r = address_new(&address);
if (r < 0) {
@ -69,10 +143,13 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr
return;
}
assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
address->family = AF_INET6;
address->in_addr.in6 = *prefix;
r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix address: %m");
return;
}
if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8);
else {
@ -102,17 +179,33 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr
link->ndisc_messages++;
}
static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen, unsigned lifetime, void *userdata) {
static void ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_route_free_ Route *route = NULL;
Link *link = userdata;
usec_t time_now;
uint32_t lifetime;
unsigned prefixlen;
int r;
assert(nd);
assert(link);
assert(rt);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix length: %m");
return;
}
r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix lifetime: %m");
return;
}
r = route_new(&route);
if (r < 0) {
@ -120,16 +213,19 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre
return;
}
assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->flags = RTM_F_PREFIX;
route->dst.in6 = *prefix;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix address: %m");
return;
}
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set prefix route: %m");
@ -140,32 +236,47 @@ static void ndisc_prefix_onlink_handler(sd_ndisc *nd, const struct in6_addr *pre
link->ndisc_messages++;
}
static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) {
static void ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
_cleanup_route_free_ Route *route = NULL;
Link *link = userdata;
struct in6_addr gateway;
uint32_t lifetime;
unsigned preference, prefixlen;
usec_t time_now;
int r;
assert(link);
assert(link->network);
assert(link->manager);
assert(link->dhcp6_client);
assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
r = sd_ndisc_router_route_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
if (lifetime == 0)
return;
if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
/* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
r = dhcp6_request_address(link, flags & ND_RA_FLAG_MANAGED ? false : true);
if (r < 0 && r != -EBUSY)
log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
else
log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
r = sd_ndisc_router_get_address(rt, &gateway);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
return;
}
if (!gateway)
r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
return;
}
r = sd_ndisc_router_route_get_preference(rt, &preference);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
return;
}
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = route_new(&route);
if (r < 0) {
@ -173,18 +284,23 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a
return;
}
assert_se(sd_event_now(link->manager->event, clock_boottime_or_monotonic(), &time_now) >= 0);
route->family = AF_INET6;
route->table = RT_TABLE_MAIN;
route->protocol = RTPROT_RA;
route->pref = pref;
route->gw.in6 = *gateway;
route->pref = preference;
route->gw.in6 = gateway;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get route address: %m");
return;
}
r = route_configure(route, link, ndisc_netlink_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set default route: %m");
log_link_warning_errno(link, r, "Could not set additional route: %m");
link_enter_failed(link);
return;
}
@ -192,7 +308,290 @@ static void ndisc_router_handler(sd_ndisc *nd, uint8_t flags, const struct in6_a
link->ndisc_messages++;
}
static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
static void ndisc_rdnss_hash_func(const void *p, struct siphash *state) {
const NDiscRDNSS *x = p;
siphash24_compress(&x->address, sizeof(x->address), state);
}
static int ndisc_rdnss_compare_func(const void *_a, const void *_b) {
const NDiscRDNSS *a = _a, *b = _b;
return memcmp(&a->address, &b->address, sizeof(a->address));
}
static const struct hash_ops ndisc_rdnss_hash_ops = {
.hash = ndisc_rdnss_hash_func,
.compare = ndisc_rdnss_compare_func
};
static void ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
uint32_t lifetime;
const struct in6_addr *a;
usec_t time_now;
int i, n, r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
return;
}
n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
if (n < 0) {
log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m");
return;
}
for (i = 0; i < n; i++) {
NDiscRDNSS d = {
.address = a[i]
}, *x;
if (lifetime == 0) {
(void) set_remove(link->ndisc_rdnss, &d);
link_dirty(link);
continue;
}
x = set_get(link->ndisc_rdnss, &d);
if (x) {
x->valid_until = time_now + lifetime * USEC_PER_SEC;
continue;
}
ndisc_vacuum(link);
if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) {
log_link_warning(link, "Too many RDNSS records per link, ignoring.");
continue;
}
r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops);
if (r < 0) {
log_oom();
return;
}
x = new0(NDiscRDNSS, 1);
if (!x) {
log_oom();
return;
}
x->address = a[i];
x->valid_until = time_now + lifetime * USEC_PER_SEC;
r = set_put(link->ndisc_rdnss, x);
if (r < 0) {
free(x);
log_oom();
return;
}
assert(r > 0);
link_dirty(link);
}
}
static void ndisc_dnssl_hash_func(const void *p, struct siphash *state) {
const NDiscDNSSL *x = p;
siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state);
}
static int ndisc_dnssl_compare_func(const void *_a, const void *_b) {
const NDiscDNSSL *a = _a, *b = _b;
return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b));
}
static const struct hash_ops ndisc_dnssl_hash_ops = {
.hash = ndisc_dnssl_hash_func,
.compare = ndisc_dnssl_compare_func
};
static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
_cleanup_strv_free_ char **l = NULL;
uint32_t lifetime;
usec_t time_now;
char **i;
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
return;
}
r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
return;
}
r = sd_ndisc_router_dnssl_get_domains(rt, &l);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m");
return;
}
STRV_FOREACH(i, l) {
struct {
NDiscDNSSL header;
char domain[strlen(*i)];
} s;
NDiscDNSSL *x;
zero(s.header);
strcpy(s.domain, *i);
if (lifetime == 0) {
(void) set_remove(link->ndisc_dnssl, &s);
link_dirty(link);
continue;
}
x = set_get(link->ndisc_dnssl, &s);
if (x) {
x->valid_until = time_now + lifetime * USEC_PER_SEC;
continue;
}
ndisc_vacuum(link);
if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) {
log_link_warning(link, "Too many DNSSL records per link, ignoring.");
continue;
}
r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops);
if (r < 0) {
log_oom();
return;
}
x = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1);
if (!x) {
log_oom();
return;
}
strcpy(NDISC_DNSSL_DOMAIN(x), *i);
x->valid_until = time_now + lifetime * USEC_PER_SEC;
r = set_put(link->ndisc_dnssl, x);
if (r < 0) {
free(x);
log_oom();
return;
}
assert(r > 0);
link_dirty(link);
}
}
static void ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
int r;
assert(link);
assert(rt);
r = sd_ndisc_router_option_rewind(rt);
for (;;) {
uint8_t type;
if (r < 0) {
log_link_warning_errno(link, r, "Failed to iterate through options: %m");
return;
}
if (r == 0) /* EOF */
break;
r = sd_ndisc_router_option_get_type(rt, &type);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA option type: %m");
return;
}
switch (type) {
case SD_NDISC_OPTION_PREFIX_INFORMATION: {
uint8_t flags;
r = sd_ndisc_router_prefix_get_flags(rt, &flags);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m");
return;
}
if (flags & ND_OPT_PI_FLAG_ONLINK)
ndisc_router_process_onlink_prefix(link, rt);
if (flags & ND_OPT_PI_FLAG_AUTO)
ndisc_router_process_autonomous_prefix(link, rt);
break;
}
case SD_NDISC_OPTION_ROUTE_INFORMATION:
ndisc_router_process_route(link, rt);
break;
case SD_NDISC_OPTION_RDNSS:
ndisc_router_process_rdnss(link, rt);
break;
case SD_NDISC_OPTION_DNSSL:
ndisc_router_process_dnssl(link, rt);
break;
}
r = sd_ndisc_router_option_next(rt);
}
}
static void ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
uint64_t flags;
int r;
assert(link);
assert(link->network);
assert(link->manager);
assert(rt);
r = sd_ndisc_router_get_flags(rt, &flags);
if (r < 0) {
log_link_warning_errno(link, r, "Failed to get RA flags: %m");
return;
}
if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
/* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
if (r < 0 && r != -EBUSY)
log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
else
log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
}
ndisc_router_process_default(link, rt);
ndisc_router_process_options(link, rt);
}
static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
Link *link = userdata;
assert(link);
@ -201,12 +600,15 @@ static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
return;
switch (event) {
case SD_NDISC_EVENT_ROUTER:
ndisc_router_handler(link, rt);
break;
case SD_NDISC_EVENT_TIMEOUT:
link->ndisc_configured = true;
link_check_ready(link);
break;
case SD_NDISC_EVENT_STOP:
break;
default:
log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
@ -216,30 +618,52 @@ static void ndisc_handler(sd_ndisc *nd, int event, void *userdata) {
int ndisc_configure(Link *link) {
int r;
assert_return(link, -EINVAL);
assert(link);
r = sd_ndisc_new(&link->ndisc_router_discovery);
r = sd_ndisc_new(&link->ndisc);
if (r < 0)
return r;
r = sd_ndisc_attach_event(link->ndisc_router_discovery, NULL, 0);
r = sd_ndisc_attach_event(link->ndisc, NULL, 0);
if (r < 0)
return r;
r = sd_ndisc_set_mac(link->ndisc_router_discovery, &link->mac);
r = sd_ndisc_set_mac(link->ndisc, &link->mac);
if (r < 0)
return r;
r = sd_ndisc_set_ifindex(link->ndisc_router_discovery, link->ifindex);
r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex);
if (r < 0)
return r;
r = sd_ndisc_set_callback(link->ndisc_router_discovery,
ndisc_router_handler,
ndisc_prefix_onlink_handler,
ndisc_prefix_autonomous_handler,
ndisc_handler,
link);
r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link);
if (r < 0)
return r;
return r;
return 0;
}
void ndisc_vacuum(Link *link) {
NDiscRDNSS *r;
NDiscDNSSL *d;
Iterator i;
usec_t time_now;
assert(link);
/* Removes all RDNSS and DNSSL entries whose validity time has passed */
time_now = now(clock_boottime_or_monotonic());
SET_FOREACH(r, link->ndisc_rdnss, i)
if (r->valid_until < time_now) {
(void) set_remove(link->ndisc_rdnss, r);
link_dirty(link);
}
SET_FOREACH(d, link->ndisc_dnssl, i)
if (d->valid_until < time_now) {
(void) set_remove(link->ndisc_dnssl, d);
link_dirty(link);
}
}

View File

@ -0,0 +1,39 @@
#pragma once
/***
This file is part of systemd.
Copyright 2013 Tom Gundersen <teg@jklm.no>
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "networkd-link.h"
typedef struct NDiscRDNSS {
usec_t valid_until;
struct in6_addr address;
} NDiscRDNSS;
typedef struct NDiscDNSSL {
usec_t valid_until;
/* The domain name follows immediately. */
} NDiscDNSSL;
static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) {
return ((char*) n) + ALIGN(sizeof(NDiscDNSSL));
}
int ndisc_configure(Link *link);
void ndisc_vacuum(Link *link);

View File

@ -89,6 +89,8 @@ DHCP.DUIDRawData, config_parse_duid_rawdata,
DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
DHCP.IAID, config_parse_iaid, 0, offsetof(Network, iaid)
IPv6AcceptRouterAdvertisements.UseDNS config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
IPv6AcceptRouterAdvertisements.UseDomains config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns)

View File

@ -51,8 +51,8 @@ static int network_load_one(Manager *manager, const char *filename) {
if (!file) {
if (errno == ENOENT)
return 0;
else
return -errno;
return -errno;
}
if (null_or_empty_fd(fileno(file))) {
@ -134,6 +134,7 @@ static int network_load_one(Manager *manager, const char *filename) {
network->ipv6_hop_limit = -1;
network->duid.type = _DUID_TYPE_INVALID;
network->proxy_arp = -1;
network->ipv6_accept_ra_use_dns = true;
r = config_parse(NULL, filename, file,
"Match\0"

View File

@ -154,6 +154,9 @@ struct Network {
int ipv6_hop_limit;
int proxy_arp;
bool ipv6_accept_ra_use_dns;
DHCPUseDomains ipv6_accept_ra_use_domains;
union in_addr_union ipv6_token;
IPv6PrivacyExtensions ipv6_privacy_extensions;

View File

@ -23,6 +23,7 @@
#include <inttypes.h>
#include <net/ethernet.h>
#include <sys/types.h>
#include "sd-event.h"
@ -30,9 +31,6 @@
_SD_BEGIN_DECLARATIONS;
typedef struct sd_lldp sd_lldp;
typedef struct sd_lldp_neighbor sd_lldp_neighbor;
/* IEEE 802.3AB Clause 9: TLV Types */
enum {
SD_LLDP_TYPE_END = 0,
@ -111,6 +109,9 @@ enum {
SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7,
};
typedef struct sd_lldp sd_lldp;
typedef struct sd_lldp_neighbor sd_lldp_neighbor;
typedef enum sd_lldp_event {
SD_LLDP_EVENT_ADDED = 'a',
SD_LLDP_EVENT_REMOVED = 'r',
@ -120,7 +121,8 @@ typedef enum sd_lldp_event {
typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata);
int sd_lldp_new(sd_lldp **ret, int ifindex);
int sd_lldp_new(sd_lldp **ret);
sd_lldp* sd_lldp_ref(sd_lldp *lldp);
sd_lldp* sd_lldp_unref(sd_lldp *lldp);
int sd_lldp_start(sd_lldp *lldp);
@ -128,8 +130,10 @@ int sd_lldp_stop(sd_lldp *lldp);
int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority);
int sd_lldp_detach_event(sd_lldp *lldp);
sd_event *sd_lldp_get_event(sd_lldp *lldp);
int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata);
int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex);
/* Controls how much and what to store in the neighbors database */
int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n);
@ -145,6 +149,7 @@ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
/* Access to LLDP frame metadata */
int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
@ -152,7 +157,7 @@ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const vo
int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret);
int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);

View File

@ -22,6 +22,8 @@
#include <inttypes.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include "sd-event.h"
@ -29,55 +31,99 @@
_SD_BEGIN_DECLARATIONS;
/* Neightbor Discovery Options, RFC 4861, Section 4.6 and
* https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */
enum {
SD_NDISC_EVENT_STOP = 0,
SD_NDISC_EVENT_TIMEOUT = 1,
SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1,
SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2,
SD_NDISC_OPTION_PREFIX_INFORMATION = 3,
SD_NDISC_OPTION_MTU = 5,
SD_NDISC_OPTION_ROUTE_INFORMATION = 24,
SD_NDISC_OPTION_RDNSS = 25,
SD_NDISC_OPTION_FLAGS_EXTENSION = 26,
SD_NDISC_OPTION_DNSSL = 31,
SD_NDISC_OPTION_CAPTIVE_PORTAL = 37,
};
/* Route preference, RFC 4191, Section 2.1 */
enum {
SD_NDISC_PREFERENCE_LOW = 3U,
SD_NDISC_PREFERENCE_MEDIUM = 0U,
SD_NDISC_PREFERENCE_HIGH = 1U,
};
typedef struct sd_ndisc sd_ndisc;
typedef struct sd_ndisc_router sd_ndisc_router;
typedef void(*sd_ndisc_router_callback_t)(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata);
typedef void(*sd_ndisc_prefix_onlink_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
unsigned lifetime, void *userdata);
typedef void(*sd_ndisc_prefix_autonomous_callback_t)(sd_ndisc *nd, const struct in6_addr *prefix, unsigned prefixlen,
unsigned lifetime_prefered, unsigned lifetime_valid, void *userdata);
typedef void(*sd_ndisc_callback_t)(sd_ndisc *nd, int event, void *userdata);
typedef enum sd_ndisc_event {
SD_NDISC_EVENT_TIMEOUT = 't',
SD_NDISC_EVENT_ROUTER = 'r',
} sd_ndisc_event;
int sd_ndisc_set_callback(sd_ndisc *nd,
sd_ndisc_router_callback_t rcb,
sd_ndisc_prefix_onlink_callback_t plcb,
sd_ndisc_prefix_autonomous_callback_t pacb,
sd_ndisc_callback_t cb,
void *userdata);
int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata);
int sd_ndisc_new(sd_ndisc **ret);
sd_ndisc *sd_ndisc_ref(sd_ndisc *nd);
sd_ndisc *sd_ndisc_unref(sd_ndisc *nd);
int sd_ndisc_start(sd_ndisc *nd);
int sd_ndisc_stop(sd_ndisc *nd);
int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority);
int sd_ndisc_detach_event(sd_ndisc *nd);
sd_event *sd_ndisc_get_event(sd_ndisc *nd);
sd_ndisc *sd_ndisc_ref(sd_ndisc *nd);
sd_ndisc *sd_ndisc_unref(sd_ndisc *nd);
int sd_ndisc_new(sd_ndisc **ret);
int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata);
int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu);
int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret);
int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret);
int sd_ndisc_stop(sd_ndisc *nd);
int sd_ndisc_router_discovery_start(sd_ndisc *nd);
int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size);
sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt);
sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt);
#define SD_NDISC_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
#define SD_NDISC_ADDRESS_FORMAT_VAL(address) \
be16toh((address).s6_addr16[0]), \
be16toh((address).s6_addr16[1]), \
be16toh((address).s6_addr16[2]), \
be16toh((address).s6_addr16[3]), \
be16toh((address).s6_addr16[4]), \
be16toh((address).s6_addr16[5]), \
be16toh((address).s6_addr16[6]), \
be16toh((address).s6_addr16[7])
int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret);
int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags);
int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret);
int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime);
int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret);
/* Generic option access */
int sd_ndisc_router_option_rewind(sd_ndisc_router *rt);
int sd_ndisc_router_option_next(sd_ndisc_router *rt);
int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret);
int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type);
int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size);
/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */
int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret);
int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret);
int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret);
int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */
int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr);
int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen);
int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret);
/* Specific option access: SD_NDISC_OPTION_RDNSS */
int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret);
int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
/* Specific option access: SD_NDISC_OPTION_DNSSL */
int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret);
int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref);
_SD_END_DECLARATIONS;

View File

@ -159,7 +159,31 @@ static void test_le(void) {
assert_se(memcmp(&scratch[7], &data[7], sizeof(uint64_t)) == 0);
}
static void test_ne(void) {
uint16_t x = 4711;
uint32_t y = 123456;
uint64_t z = 9876543210;
/* Note that we don't bother actually testing alignment issues in this function, after all the _ne() functions
* are just aliases for the _le() or _be() implementations, which we test extensively above. Hence, in this
* function, just ensure that they map to the right version on the local architecture. */
assert_se(unaligned_read_ne16(&x) == 4711);
assert_se(unaligned_read_ne32(&y) == 123456);
assert_se(unaligned_read_ne64(&z) == 9876543210);
unaligned_write_ne16(&x, 1);
unaligned_write_ne32(&y, 2);
unaligned_write_ne64(&z, 3);
assert_se(x == 1);
assert_se(y == 2);
assert_se(z == 3);
}
int main(int argc, const char *argv[]) {
test_be();
test_le();
test_ne();
return 0;
}