diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 44c15f90446..070e94add1b 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -43,6 +43,7 @@ int icmp6_bind(int ifindex, bool is_router) { }; ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); + ICMP6_FILTER_SETPASS(ND_REDIRECT, &filter); } s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 46a6119c6b6..27922591cc0 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -29,6 +29,7 @@ sources = files( 'sd-lldp-tx.c', 'sd-ndisc.c', 'sd-ndisc-neighbor.c', + 'sd-ndisc-redirect.c', 'sd-ndisc-router.c', 'sd-radv.c', ) diff --git a/src/libsystemd-network/ndisc-redirect-internal.h b/src/libsystemd-network/ndisc-redirect-internal.h new file mode 100644 index 00000000000..e35263a05dd --- /dev/null +++ b/src/libsystemd-network/ndisc-redirect-internal.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-ndisc.h" + +#include "icmp6-packet.h" +#include "set.h" + +struct sd_ndisc_redirect { + unsigned n_ref; + + ICMP6Packet *packet; + + struct in6_addr target_address; + struct in6_addr destination_address; + + Set *options; +}; + +sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet); +int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd); diff --git a/src/libsystemd-network/sd-ndisc-redirect.c b/src/libsystemd-network/sd-ndisc-redirect.c new file mode 100644 index 00000000000..3e21b76fffe --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-redirect.c @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "ndisc-internal.h" +#include "ndisc-option.h" +#include "ndisc-redirect-internal.h" + +static sd_ndisc_redirect* ndisc_redirect_free(sd_ndisc_redirect *rd) { + if (!rd) + return NULL; + + icmp6_packet_unref(rd->packet); + set_free(rd->options); + return mfree(rd); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_redirect, sd_ndisc_redirect, ndisc_redirect_free); + +sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet) { + sd_ndisc_redirect *rd; + + assert(packet); + + rd = new(sd_ndisc_redirect, 1); + if (!rd) + return NULL; + + *rd = (sd_ndisc_redirect) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + }; + + return rd; +} + +int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd) { + int r; + + assert(rd); + assert(rd->packet); + + if (rd->packet->raw_size < sizeof(struct nd_redirect)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a redirect message, ignoring."); + + const struct nd_redirect *a = (const struct nd_redirect*) rd->packet->raw_packet; + assert(a->nd_rd_type == ND_REDIRECT); + assert(a->nd_rd_code == 0); + + rd->target_address = a->nd_rd_target; + rd->destination_address = a->nd_rd_dst; + + if (in6_addr_is_null(&rd->target_address) || in6_addr_is_multicast(&rd->target_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Redirect message with an invalid target address, ignoring datagram: %m"); + + if (in6_addr_is_null(&rd->destination_address) || in6_addr_is_multicast(&rd->destination_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Redirect message with an invalid destination address, ignoring datagram: %m"); + + r = ndisc_parse_options(rd->packet, &rd->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in Redirect message, ignoring datagram: %m"); + + return 0; +} + +int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr) { + assert_return(rd, -EINVAL); + + return icmp6_packet_set_sender_address(rd->packet, addr); +} + +int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + return icmp6_packet_get_sender_address(rd->packet, ret); +} + +int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + if (in6_addr_is_null(&rd->target_address)) + return -ENODATA; + + if (ret) + *ret = rd->target_address; + return 0; +} + +int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + if (in6_addr_is_null(&rd->destination_address)) + return -ENODATA; + + if (ret) + *ret = rd->destination_address; + return 0; +} + +int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *rd, struct ether_addr *ret) { + assert_return(rd, -EINVAL); + + return ndisc_option_get_mac(rd->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret); +} + +int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *rd, struct ip6_hdr *ret) { + assert_return(rd, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get_by_type(rd->options, SD_NDISC_OPTION_REDIRECTED_HEADER); + if (!p) + return -ENODATA; + + if (ret) + *ret = p->hdr; + return 0; +} diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 373e7a45ce6..cba9470ce07 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -17,6 +17,7 @@ #include "memory-util.h" #include "ndisc-internal.h" #include "ndisc-neighbor-internal.h" +#include "ndisc-redirect-internal.h" #include "ndisc-router-internal.h" #include "network-common.h" #include "random-util.h" @@ -30,6 +31,7 @@ static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { [SD_NDISC_EVENT_TIMEOUT] = "timeout", [SD_NDISC_EVENT_ROUTER] = "router", [SD_NDISC_EVENT_NEIGHBOR] = "neighbor", + [SD_NDISC_EVENT_REDIRECT] = "redirect", }; DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t); @@ -257,6 +259,35 @@ static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) { return 0; } +static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) { + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL; + struct in6_addr a; + int r; + + assert(nd); + assert(packet); + + rd = ndisc_redirect_new(packet); + if (!rd) + return -ENOMEM; + + r = ndisc_redirect_parse(nd, rd); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_sender_address(rd, &a); + if (r < 0) + return r; + + log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s", + IN6_ADDR_TO_STRING(&a), + IN6_ADDR_TO_STRING(&rd->target_address), + IN6_ADDR_TO_STRING(&rd->destination_address)); + + ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd); + return 0; +} + static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; sd_ndisc *nd = ASSERT_PTR(userdata); @@ -298,6 +329,10 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda (void) ndisc_handle_neighbor(nd, packet); break; + case ND_REDIRECT: + (void) ndisc_handle_redirect(nd, packet); + break; + default: log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r); } diff --git a/src/systemd/meson.build b/src/systemd/meson.build index 389f32908c6..e188bb99b87 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -40,6 +40,7 @@ _not_installed_headers = [ 'sd-ndisc.h', 'sd-ndisc-neighbor.h', 'sd-ndisc-protocol.h', + 'sd-ndisc-redirect.h', 'sd-ndisc-router.h', 'sd-netlink.h', 'sd-network.h', diff --git a/src/systemd/sd-ndisc-redirect.h b/src/systemd/sd-ndisc-redirect.h new file mode 100644 index 00000000000..60b43f769de --- /dev/null +++ b/src/systemd/sd-ndisc-redirect.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscredirectfoo +#define foosdndiscredirectfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_redirect sd_ndisc_redirect; + +sd_ndisc_redirect* sd_ndisc_redirect_ref(sd_ndisc_redirect *na); +sd_ndisc_redirect* sd_ndisc_redirect_unref(sd_ndisc_redirect *na); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_redirect, sd_ndisc_redirect_unref); + +int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr); +int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *na, struct ether_addr *ret); +int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *na, struct ip6_hdr *ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index 185e0c6fd82..cce592f9e38 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -28,6 +28,7 @@ #include "sd-event.h" #include "sd-ndisc-neighbor.h" #include "sd-ndisc-protocol.h" +#include "sd-ndisc-redirect.h" #include "sd-ndisc-router.h" #include "_sd-common.h" @@ -40,6 +41,7 @@ __extension__ typedef enum sd_ndisc_event_t { SD_NDISC_EVENT_TIMEOUT, SD_NDISC_EVENT_ROUTER, SD_NDISC_EVENT_NEIGHBOR, + SD_NDISC_EVENT_REDIRECT, _SD_NDISC_EVENT_MAX, _SD_NDISC_EVENT_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(NDISC_EVENT)