summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/option6_pdexclude.cc
blob: b71a820cbea56dd23e43f99a1e4786b792c728f9 (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option6_pdexclude.h>
#include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <util/io_utilities.h>

#include <boost/dynamic_bitset.hpp>
#include <iostream>
#include <stdint.h>

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;

namespace isc {
namespace dhcp {

Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
                                   const uint8_t delegated_prefix_length,
                                   const isc::asiolink::IOAddress& excluded_prefix,
                                   const uint8_t excluded_prefix_length)
    : Option(V6, D6O_PD_EXCLUDE),
      excluded_prefix_length_(excluded_prefix_length),
      subnet_id_() {

    // Expecting v6 prefixes of sane length.
    if (!delegated_prefix.isV6() || !excluded_prefix.isV6() ||
        (delegated_prefix_length > 128) || (excluded_prefix_length_ > 128)) {
        isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
                  << delegated_prefix << "/"
                  << static_cast<int>(delegated_prefix_length) << ", "
                  << excluded_prefix << "/"
                  << static_cast<int>(excluded_prefix_length_));
    }

    // Excluded prefix must be longer than the delegated prefix length.
    if (excluded_prefix_length_ <= delegated_prefix_length) {
        isc_throw(BadValue, "length of the excluded prefix "
                  << excluded_prefix << "/"
                  << static_cast<int>(excluded_prefix_length_)
                  << " must be greater than the length of the"
                  " delegated prefix " << delegated_prefix << "/"
                  << static_cast<int>(delegated_prefix_length));
    }

    // Both prefixes must share common part with a length equal to the
    // delegated prefix length.
    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
    boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
                                                         delegated_prefix_bytes.rend());

    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix.toBytes();
    boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
                                                        excluded_prefix_bytes.rend());


    // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
    const uint8_t delta = 128 - delegated_prefix_length;

    if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
        isc_throw(BadValue, "excluded prefix "
                  << excluded_prefix << "/"
                  << static_cast<int>(excluded_prefix_length_)
                  << " must have the same common prefix part of "
                  << static_cast<int>(delegated_prefix_length)
                  << " as the delegated prefix "
                  << delegated_prefix << "/"
                  << static_cast<int>(delegated_prefix_length));
    }


    // Shifting prefix by delegated prefix length leaves us with only a
    // subnet id part of the excluded prefix.
    excluded_prefix_bits <<= delegated_prefix_length;

    // Calculate subnet id length.
    const uint8_t subnet_id_length = getSubnetIDLength(delegated_prefix_length,
                                                       excluded_prefix_length);
    for (uint8_t i = 0; i < subnet_id_length; ++i) {
        // Retrieve bit representation of the current byte.
        const boost::dynamic_bitset<uint8_t> first_byte = excluded_prefix_bits >> 120;

        // Convert it to a numeric value.
        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());

        // Zero padded excluded_prefix_bits follow when excluded_prefix_length_ is
        // not divisible by 8.
        if (i == subnet_id_length - 1) {
            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length;
            if (length_delta % 8 != 0) {
                uint8_t mask = 0xFF;
                mask <<= (8 - (length_delta % 8));
                val &= mask;
            }
        }
        // Store calculated value in a buffer.
        subnet_id_.push_back(val);

        // Go to the next byte.
        excluded_prefix_bits <<= 8;
    }
}

Option6PDExclude::Option6PDExclude(OptionBufferConstIter begin,
                                   OptionBufferConstIter end)
    : Option(V6, D6O_PD_EXCLUDE),
      excluded_prefix_length_(0),
      subnet_id_() {
    unpack(begin, end);
}

OptionPtr
Option6PDExclude::clone() const {
    return (cloneInternal<Option6PDExclude>());
}

void
Option6PDExclude::pack(isc::util::OutputBuffer& buf, bool) const {
    // Make sure that the subnet identifier is valid. It should never
    // be empty.
    if ((excluded_prefix_length_ == 0) || subnet_id_.empty()) {
        isc_throw(BadValue, "subnet identifier of a Prefix Exclude option"
                  " must not be empty");
    }

    // Header = option code and length.
    packHeader(buf);

    // Excluded prefix length is always 1 byte long field.
    buf.writeUint8(excluded_prefix_length_);

    // Write the subnet identifier.
    buf.writeData(static_cast<const void*>(&subnet_id_[0]), subnet_id_.size());
}

void
Option6PDExclude::unpack(OptionBufferConstIter begin,
                         OptionBufferConstIter end) {

    // At this point we don't know the excluded prefix length, but the
    // minimum requirement is that reminder of this option includes the
    // excluded prefix length and at least 1 byte of the IPv6 subnet id.
    if (std::distance(begin, end) < 2) {
        isc_throw(BadValue, "truncated Prefix Exclude option");
    }

    // We can safely read the excluded prefix length and move forward.
    uint8_t excluded_prefix_length = *begin++;
    if (excluded_prefix_length == 0) {
        isc_throw(BadValue, "excluded prefix length must not be 0");
    }

    std::vector<uint8_t> subnet_id_bytes(begin, end);

    // Subnet id parsed, proceed to the end of the option.
    begin = end;

    uint8_t last_bits_num = excluded_prefix_length % 8;
    if (last_bits_num > 0) {
        *subnet_id_bytes.rbegin() = (*subnet_id_bytes.rbegin() >> (8 - last_bits_num)
                                     << (8 - (last_bits_num)));
    }

    excluded_prefix_length_ = excluded_prefix_length;
    subnet_id_.swap(subnet_id_bytes);
}

uint16_t
Option6PDExclude::len() const {
    return (getHeaderLen() + sizeof(excluded_prefix_length_) + subnet_id_.size());
}

std::string
Option6PDExclude::toText(int indent) const {
    std::ostringstream s;
    s << headerToText(indent) << ": ";
    s << "excluded-prefix-len=" << static_cast<unsigned>(excluded_prefix_length_)
      << ", subnet-id=0x" << util::encode::encodeHex(subnet_id_);
    return (s.str());
}

asiolink::IOAddress
Option6PDExclude::getExcludedPrefix(const IOAddress& delegated_prefix,
                                    const uint8_t delegated_prefix_length) const {
    // Get binary representation of the delegated prefix.
    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
    //  We need to calculate how many bytes include the useful data and assign
    // zeros to remaining bytes (beyond the prefix length).
    const uint8_t bytes_length = (delegated_prefix_length / 8) +
        static_cast<uint8_t>(delegated_prefix_length % 8 != 0);
    std::fill(delegated_prefix_bytes.begin() + bytes_length,
              delegated_prefix_bytes.end(), 0);

    // Convert the delegated prefix to bit format.
    boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
                                        delegated_prefix_bytes.rend());

    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_.rbegin(),
                                                  subnet_id_.rend());

    // Concatenate the delegated prefix with subnet id. The resulting prefix
    // is an excluded prefix in bit format.
    for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
        bits.set(128 - delegated_prefix_length - subnet_id_bits.size() + i,
                 subnet_id_bits.test(i));
    }

    // Convert the prefix to binary format.
    std::vector<uint8_t> bytes(V6ADDRESS_LEN);
    boost::to_block_range(bits, bytes.rbegin());

    // And create a prefix object from bytes.
    return (IOAddress::fromBytes(AF_INET6, &bytes[0]));
}

uint8_t
Option6PDExclude::getSubnetIDLength(const uint8_t delegated_prefix_length,
                                    const uint8_t excluded_prefix_length) const {
    uint8_t subnet_id_length_bits = excluded_prefix_length -
        delegated_prefix_length - 1;
    uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
    return (subnet_id_length);
}

} // end of namespace isc::dhcp
} // end of namespace isc