Coverage Report

Created: 2025-12-31 07:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kea/src/lib/dhcp/option6_pdexclude.cc
Line
Count
Source
1
// Copyright (C) 2016-2024 Internet Systems Consortium, Inc. ("ISC")
2
//
3
// This Source Code Form is subject to the terms of the Mozilla Public
4
// License, v. 2.0. If a copy of the MPL was not distributed with this
5
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7
#include <config.h>
8
9
#include <asiolink/io_address.h>
10
#include <dhcp/dhcp6.h>
11
#include <dhcp/option6_pdexclude.h>
12
#include <exceptions/exceptions.h>
13
#include <util/encode/encode.h>
14
15
#include <boost/dynamic_bitset.hpp>
16
#include <iostream>
17
#include <stdint.h>
18
19
using namespace std;
20
using namespace isc;
21
using namespace isc::dhcp;
22
using namespace isc::asiolink;
23
using namespace isc::util;
24
25
namespace isc {
26
namespace dhcp {
27
28
Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
29
                                   const uint8_t delegated_prefix_length,
30
                                   const isc::asiolink::IOAddress& excluded_prefix,
31
                                   const uint8_t excluded_prefix_length)
32
0
    : Option(V6, D6O_PD_EXCLUDE),
33
0
      excluded_prefix_length_(excluded_prefix_length),
34
0
      subnet_id_() {
35
36
    // Expecting v6 prefixes of sane length.
37
0
    if (!delegated_prefix.isV6() || !excluded_prefix.isV6() ||
38
0
        (delegated_prefix_length > 128) || (excluded_prefix_length_ > 128)) {
39
0
        isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
40
0
                  << delegated_prefix << "/"
41
0
                  << static_cast<int>(delegated_prefix_length) << ", "
42
0
                  << excluded_prefix << "/"
43
0
                  << static_cast<int>(excluded_prefix_length_));
44
0
    }
45
46
    // Excluded prefix must be longer than the delegated prefix length.
47
0
    if (excluded_prefix_length_ <= delegated_prefix_length) {
48
0
        isc_throw(BadValue, "length of the excluded prefix "
49
0
                  << excluded_prefix << "/"
50
0
                  << static_cast<int>(excluded_prefix_length_)
51
0
                  << " must be greater than the length of the"
52
0
                  " delegated prefix " << delegated_prefix << "/"
53
0
                  << static_cast<int>(delegated_prefix_length));
54
0
    }
55
56
    // Both prefixes must share common part with a length equal to the
57
    // delegated prefix length.
58
0
    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
59
0
    boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
60
0
                                                         delegated_prefix_bytes.rend());
61
62
0
    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix.toBytes();
63
0
    boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
64
0
                                                        excluded_prefix_bytes.rend());
65
66
67
    // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
68
0
    const uint8_t delta = 128 - delegated_prefix_length;
69
70
0
    if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
71
0
        isc_throw(BadValue, "excluded prefix "
72
0
                  << excluded_prefix << "/"
73
0
                  << static_cast<int>(excluded_prefix_length_)
74
0
                  << " must have the same common prefix part of "
75
0
                  << static_cast<int>(delegated_prefix_length)
76
0
                  << " as the delegated prefix "
77
0
                  << delegated_prefix << "/"
78
0
                  << static_cast<int>(delegated_prefix_length));
79
0
    }
80
81
82
    // Shifting prefix by delegated prefix length leaves us with only a
83
    // subnet id part of the excluded prefix.
84
0
    excluded_prefix_bits <<= delegated_prefix_length;
85
86
    // Calculate subnet id length.
87
0
    const uint8_t subnet_id_length = getSubnetIDLength(delegated_prefix_length,
88
0
                                                       excluded_prefix_length);
89
0
    for (uint8_t i = 0; i < subnet_id_length; ++i) {
90
        // Retrieve bit representation of the current byte.
91
0
        const boost::dynamic_bitset<uint8_t> first_byte = excluded_prefix_bits >> 120;
92
93
        // Convert it to a numeric value.
94
0
        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
95
96
        // Zero padded excluded_prefix_bits follow when excluded_prefix_length_ is
97
        // not divisible by 8.
98
0
        if (i == subnet_id_length - 1) {
99
0
            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length;
100
0
            if (length_delta % 8 != 0) {
101
0
                uint8_t mask = 0xFF;
102
0
                mask <<= (8 - (length_delta % 8));
103
0
                val &= mask;
104
0
            }
105
0
        }
106
        // Store calculated value in a buffer.
107
0
        subnet_id_.push_back(val);
108
109
        // Go to the next byte.
110
0
        excluded_prefix_bits <<= 8;
111
0
    }
112
0
}
113
114
Option6PDExclude::Option6PDExclude(OptionBufferConstIter begin,
115
                                   OptionBufferConstIter end)
116
3.78k
    : Option(V6, D6O_PD_EXCLUDE),
117
3.78k
      excluded_prefix_length_(0),
118
3.78k
      subnet_id_() {
119
3.78k
    unpack(begin, end);
120
3.78k
}
121
122
OptionPtr
123
0
Option6PDExclude::clone() const {
124
0
    return (cloneInternal<Option6PDExclude>());
125
0
}
126
127
void
128
575
Option6PDExclude::pack(isc::util::OutputBuffer& buf, bool) const {
129
    // Make sure that the subnet identifier is valid. It should never
130
    // be empty.
131
575
    if ((excluded_prefix_length_ == 0) || subnet_id_.empty()) {
132
0
        isc_throw(BadValue, "subnet identifier of a Prefix Exclude option"
133
0
                  " must not be empty");
134
0
    }
135
136
    // Header = option code and length.
137
575
    packHeader(buf);
138
139
    // Excluded prefix length is always 1 byte long field.
140
575
    buf.writeUint8(excluded_prefix_length_);
141
142
    // Write the subnet identifier.
143
575
    buf.writeData(static_cast<const void*>(&subnet_id_[0]), subnet_id_.size());
144
575
}
145
146
void
147
Option6PDExclude::unpack(OptionBufferConstIter begin,
148
3.78k
                         OptionBufferConstIter end) {
149
150
    // At this point we don't know the excluded prefix length, but the
151
    // minimum requirement is that reminder of this option includes the
152
    // excluded prefix length and at least 1 byte of the IPv6 subnet id.
153
3.78k
    if (std::distance(begin, end) < 2) {
154
20
        isc_throw(BadValue, "truncated Prefix Exclude option");
155
20
    }
156
157
    // We can safely read the excluded prefix length and move forward.
158
3.76k
    uint8_t excluded_prefix_length = *begin++;
159
3.76k
    if (excluded_prefix_length == 0) {
160
22
        isc_throw(BadValue, "excluded prefix length must not be 0");
161
22
    }
162
163
3.74k
    std::vector<uint8_t> subnet_id_bytes(begin, end);
164
165
    // Subnet id parsed, proceed to the end of the option.
166
3.74k
    begin = end;
167
168
3.74k
    uint8_t last_bits_num = excluded_prefix_length % 8;
169
3.74k
    if (last_bits_num > 0) {
170
2.26k
        *subnet_id_bytes.rbegin() = (*subnet_id_bytes.rbegin() >> (8 - last_bits_num)
171
2.26k
                                     << (8 - (last_bits_num)));
172
2.26k
    }
173
174
3.74k
    excluded_prefix_length_ = excluded_prefix_length;
175
3.74k
    subnet_id_.swap(subnet_id_bytes);
176
3.74k
}
177
178
uint16_t
179
1.21k
Option6PDExclude::len() const {
180
1.21k
    return (getHeaderLen() + sizeof(excluded_prefix_length_) + subnet_id_.size());
181
1.21k
}
182
183
std::string
184
573
Option6PDExclude::toText(int indent) const {
185
573
    std::ostringstream s;
186
573
    s << headerToText(indent) << ": ";
187
573
    s << "excluded-prefix-len=" << static_cast<unsigned>(excluded_prefix_length_)
188
573
      << ", subnet-id=0x" << util::encode::encodeHex(subnet_id_);
189
573
    return (s.str());
190
573
}
191
192
asiolink::IOAddress
193
Option6PDExclude::getExcludedPrefix(const IOAddress& delegated_prefix,
194
0
                                    const uint8_t delegated_prefix_length) const {
195
    // Get binary representation of the delegated prefix.
196
0
    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
197
    //  We need to calculate how many bytes include the useful data and assign
198
    // zeros to remaining bytes (beyond the prefix length).
199
0
    const uint8_t bytes_length = (delegated_prefix_length / 8) +
200
0
        static_cast<uint8_t>(delegated_prefix_length % 8 != 0);
201
0
    std::fill(delegated_prefix_bytes.begin() + bytes_length,
202
0
              delegated_prefix_bytes.end(), 0);
203
204
    // Convert the delegated prefix to bit format.
205
0
    boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
206
0
                                        delegated_prefix_bytes.rend());
207
208
0
    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_.rbegin(),
209
0
                                                  subnet_id_.rend());
210
211
    // Concatenate the delegated prefix with subnet id. The resulting prefix
212
    // is an excluded prefix in bit format.
213
0
    for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
214
0
        bits.set(128 - delegated_prefix_length - subnet_id_bits.size() + i,
215
0
                 subnet_id_bits.test(i));
216
0
    }
217
218
    // Convert the prefix to binary format.
219
0
    std::vector<uint8_t> bytes(V6ADDRESS_LEN);
220
0
    boost::to_block_range(bits, bytes.rbegin());
221
222
    // And create a prefix object from bytes.
223
0
    return (IOAddress::fromBytes(AF_INET6, &bytes[0]));
224
0
}
225
226
uint8_t
227
Option6PDExclude::getSubnetIDLength(const uint8_t delegated_prefix_length,
228
0
                                    const uint8_t excluded_prefix_length) const {
229
0
    uint8_t subnet_id_length_bits = excluded_prefix_length -
230
0
        delegated_prefix_length - 1;
231
0
    uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
232
0
    return (subnet_id_length);
233
0
}
234
235
} // end of namespace isc::dhcp
236
} // end of namespace isc