summaryrefslogtreecommitdiffstats
path: root/src/libsystemd-network/sd-ndisc-neighbor.c
blob: 1bb6ebf610b6e2d2adac1c4c2d42a5f4ff1c5dfe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <netinet/icmp6.h>

#include "sd-ndisc.h"

#include "alloc-util.h"
#include "in-addr-util.h"
#include "ndisc-internal.h"
#include "ndisc-neighbor-internal.h"
#include "ndisc-option.h"

static sd_ndisc_neighbor* ndisc_neighbor_free(sd_ndisc_neighbor *na) {
        if (!na)
                return NULL;

        icmp6_packet_unref(na->packet);
        set_free(na->options);
        return mfree(na);
}

DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor, ndisc_neighbor_free);

sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet) {
        sd_ndisc_neighbor *na;

        assert(packet);

        na = new(sd_ndisc_neighbor, 1);
        if (!na)
                return NULL;

        *na = (sd_ndisc_neighbor) {
                .n_ref = 1,
                .packet = icmp6_packet_ref(packet),
        };

        return na;
}

int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na) {
        int r;

        assert(na);

        if (na->packet->raw_size < sizeof(struct nd_neighbor_advert))
                return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
                                       "Too small to be a neighbor advertisement, ignoring datagram.");

        /* Neighbor advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */
        const struct nd_neighbor_advert *a = (const struct nd_neighbor_advert*) na->packet->raw_packet;
        assert(a->nd_na_type == ND_NEIGHBOR_ADVERT);
        assert(a->nd_na_code == 0);

        na->flags = a->nd_na_flags_reserved; /* the first 3 bits */
        na->target_address = a->nd_na_target;

        /* RFC 4861 section 4.4:
         * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that
         * prompted this advertisement. For an unsolicited advertisement, the address whose link-layer
         * address has changed. The Target Address MUST NOT be a multicast address.
         *
         * Here, we only check if the target address is a link-layer address (or a null address, for safety)
         * when the message is an unsolicited neighbor advertisement. */
        if (!FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED))
                if (!in6_addr_is_link_local(&na->target_address) && !in6_addr_is_null(&na->target_address))
                        return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
                                               "Received ND packet with an invalid target address (%s), ignoring datagram.",
                                               IN6_ADDR_TO_STRING(&na->target_address));

        r = ndisc_parse_options(na->packet, &na->options);
        if (r < 0)
                return log_ndisc_errno(nd, r, "Failed to parse NDisc options in neighbor advertisement message, ignoring: %m");

        return 0;
}

int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret) {
        assert_return(na, -EINVAL);

        return icmp6_packet_get_sender_address(na->packet, ret);
}

int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret) {
        assert_return(na, -EINVAL);

        if (in6_addr_is_null(&na->target_address))
                /* fall back to the sender address, for safety. */
                return sd_ndisc_neighbor_get_sender_address(na, ret);

        if (ret)
                *ret = na->target_address;
        return 0;
}

int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret) {
        assert_return(na, -EINVAL);

        return ndisc_option_get_mac(na->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret);
}

int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret) {
        assert_return(na, -EINVAL);

        if (ret)
                *ret = na->flags;
        return 0;
}

int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na) {
        assert_return(na, -EINVAL);

        return FLAGS_SET(na->flags, ND_NA_FLAG_ROUTER);
}

int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na) {
        assert_return(na, -EINVAL);

        return FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED);
}

int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na) {
        assert_return(na, -EINVAL);

        return FLAGS_SET(na->flags, ND_NA_FLAG_OVERRIDE);
}