1 // Copyright (C) 2016-2021 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 #ifndef PGSQL_HOST_DATA_SOURCE_H
8 #define PGSQL_HOST_DATA_SOURCE_H
9 
10 #include <database/database_connection.h>
11 #include <dhcpsrv/base_host_data_source.h>
12 #include <pgsql/pgsql_connection.h>
13 #include <pgsql/pgsql_exchange.h>
14 
15 namespace isc {
16 namespace dhcp {
17 
18 /// Forward declaration to the implementation of the @ref PgSqlHostDataSource.
19 class PgSqlHostDataSourceImpl;
20 
21 /// @brief Type of pointers to PgSqlHostDataSourceImpl.
22 typedef boost::shared_ptr<PgSqlHostDataSourceImpl> PgSqlHostDataSourceImplPtr;
23 
24 /// Forward declaration for the thread context for the manager pool.
25 class PgSqlHostContext;
26 
27 /// @brief Type of pointers to contexts.
28 typedef boost::shared_ptr<PgSqlHostContext> PgSqlHostContextPtr;
29 
30 /// @brief PostgreSQL Host Data Source
31 ///
32 /// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
33 /// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
34 /// database is available and that the Kea schema has been created within it.
35 ///
36 /// Reservations are uniquely identified by identifier type and value.
37 /// The currently supported values are defined in @ref Host::IdentifierType
38 /// as well as in host_identifier_table:
39 ///
40 /// - IDENT_HWADDR
41 /// - IDENT_DUID
42 /// - IDENT_CIRCUIT_ID
43 /// - IDENT_CLIENT_ID
44 ///
45 class PgSqlHostDataSource : public BaseHostDataSource {
46 public:
47 
48     /// @brief Constructor
49     ///
50     /// Uses the following keywords in the parameters passed to it to
51     /// connect to the database:
52     /// - name - Name of the database to which to connect (mandatory)
53     /// - host - Host to which to connect (optional, defaults to "localhost")
54     /// - user - Username under which to connect (optional)
55     /// - password - Password for "user" on the database (optional)
56     ///
57     /// If the database is successfully opened, the version number in the
58     /// schema_version table will be checked against hard-coded value in
59     /// the implementation file.
60     ///
61     /// Finally, all the SQL commands are pre-compiled.
62     ///
63     /// @param parameters A data structure relating keywords and values
64     ///        concerned with the database.
65     ///
66     /// @throw isc::db::NoDatabaseName Mandatory database name not given
67     /// @throw isc::db::DbOpenError Error opening the database
68     /// @throw isc::db::DbOperationError An operation on the open database has
69     ///        failed.
70     PgSqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
71 
72     /// @brief Virtual destructor.
73     ///
74     /// Frees database resources and closes the database connection through
75     /// the destruction of member impl_.
76     virtual ~PgSqlHostDataSource();
77 
78     /// @brief Return backend parameters
79     ///
80     /// Returns the backend parameters
81     ///
82     /// @return Parameters of the backend.
83     virtual isc::db::DatabaseConnection::ParameterMap getParameters() const;
84 
85     /// @brief Adds a new host to the collection.
86     ///
87     /// The method will insert the given host and all of its children (v4
88     /// options, v6 options, and v6 reservations) into the database.  It
89     /// relies on constraints defined as part of the PostgreSQL schema to
90     /// defend against duplicate entries and to ensure referential
91     /// integrity.
92     ///
93     /// Violation of any of these constraints for a host will result in a
94     /// DuplicateEntry exception:
95     ///
96     /// -# IPV4_ADDRESS and DHCP4_SUBNET_ID combination must be unique
97     /// -# IPV6 ADDRESS and PREFIX_LEN combination must be unique
98     /// -# DHCP ID, DHCP ID TYPE, and DHCP4_SUBNET_ID combination must be unique
99     /// -# DHCP ID, DHCP ID TYPE, and DHCP6_SUBNET_ID combination must be unique
100     ///
101     /// In addition, violating the following referential constraints will
102     /// a DbOperationError exception:
103     ///
104     /// -# DHCP ID TYPE must be defined in the HOST_IDENTIFIER_TYPE table
105     /// -# For DHCP4 Options:
106     ///  -# HOST_ID must exist with HOSTS
107     ///  -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
108     /// -# For DHCP6 Options:
109     ///  -# HOST_ID must exist with HOSTS
110     ///  -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
111     /// -# For IPV6 Reservations:
112     ///  -# HOST_ID must exist with HOSTS
113     ///  -# Address and Prefix Length must be unique (DuplicateEntry)
114     ///
115     /// @param host Pointer to the new @c Host object being added.
116     /// @throw DuplicateEntry or DbOperationError dependent on the constraint
117     /// violation
118     virtual void add(const HostPtr& host);
119 
120     /// @brief Attempts to delete hosts by (subnet-id, address)
121     ///
122     /// This method supports both v4 and v6.
123     ///
124     /// @param subnet_id subnet identifier.
125     /// @param addr specified address.
126     /// @return true if deletion was successful, false if the host was not there.
127     /// @throw various exceptions in case of errors
128     virtual bool del(const SubnetID& subnet_id,
129                      const asiolink::IOAddress& addr);
130 
131     /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier)
132     ///
133     /// This method supports v4 hosts only.
134     ///
135     /// @param subnet_id subnet identifier.
136     /// @param identifier_type Identifier type.
137     /// @param identifier_begin Pointer to a beginning of a buffer containing
138     /// an identifier.
139     /// @param identifier_len Identifier length.
140     ///
141     /// @return true if deletion was successful, false if the host was not there.
142     /// @throw various exceptions in case of errors
143     virtual bool del4(const SubnetID& subnet_id,
144                       const Host::IdentifierType& identifier_type,
145                       const uint8_t* identifier_begin,
146                       const size_t identifier_len);
147 
148     /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier)
149     ///
150     /// This method supports v6 hosts only.
151     ///
152     /// @param subnet_id subnet identifier.
153     /// @param identifier_type Identifier type.
154     /// @param identifier_begin Pointer to a beginning of a buffer containing
155     /// an identifier.
156     /// @param identifier_len Identifier length.
157     ///
158     /// @return true if deletion was successful, false if the host was not there.
159     /// @throw various exceptions in case of errors
160     virtual bool del6(const SubnetID& subnet_id,
161                       const Host::IdentifierType& identifier_type,
162                       const uint8_t* identifier_begin,
163                       const size_t identifier_len);
164 
165     /// @brief Return all hosts connected to any subnet for which reservations
166     /// have been made using a specified identifier.
167     ///
168     /// This method returns all @c Host objects which represent reservations
169     /// for a specified identifier. This method may return multiple hosts
170     /// because a particular client may have reservations in multiple subnets.
171     ///
172     /// @param identifier_type Identifier type.
173     /// @param identifier_begin Pointer to a beginning of a buffer containing
174     /// an identifier.
175     /// @param identifier_len Identifier length.
176     ///
177     /// @return Collection of const @c Host objects.
178     virtual ConstHostCollection getAll(const Host::IdentifierType& identifier_type,
179                                        const uint8_t* identifier_begin,
180                                        const size_t identifier_len) const;
181 
182     /// @brief Return all hosts in a DHCPv4 subnet.
183     ///
184     /// This method returns all @ref Host objects which represent reservations
185     /// in a specified subnet. Global reservations are returned for the
186     /// subnet id 0.
187     ///
188     /// @param subnet_id subnet identifier to filter by
189     ///
190     /// @return Collection of const @ref Host objects.
191     virtual ConstHostCollection getAll4(const SubnetID& subnet_id) const;
192 
193     /// @brief Return all hosts in a DHCPv6 subnet.
194     ///
195     /// This method returns all @ref Host objects which represent reservations
196     /// in a specified subnet. Global reservations are returned for the
197     /// subnet id 0.
198     ///
199     /// @param subnet_id subnet identifier to filter by
200     ///
201     /// @return Collection of const @ref Host objects.
202     virtual ConstHostCollection getAll6(const SubnetID& subnet_id) const;
203 
204     /// @brief Return all hosts with a hostname.
205     ///
206     /// This method returns all @c Host objects which represent reservations
207     /// using a specified hostname.
208     ///
209     /// PostgreSQL uses the hosts_by_hostname index on LOWER(hostname).
210     ///
211     /// @param hostname The lower case hostname.
212     ///
213     /// @return Collection of const @c Host objects.
214     virtual ConstHostCollection getAllbyHostname(const std::string& hostname) const;
215 
216     /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
217     ///
218     /// This method returns all @c Host objects which represent reservations
219     /// using a specified hostname in a specified subnet.
220     ///
221     /// @param hostname The lower case hostname.
222     /// @param subnet_id Subnet identifier.
223     ///
224     /// @return Collection of const @c Host objects.
225     virtual ConstHostCollection getAllbyHostname4(const std::string& hostname,
226                                                   const SubnetID& subnet_id) const;
227 
228     /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
229     ///
230     /// This method returns all @c Host objects which represent reservations
231     /// using a specified hostname in a specified subnet.
232     ///
233     /// @param hostname The lower case hostname.
234     /// @param subnet_id Subnet identifier.
235     ///
236     /// @return Collection of const @c Host objects.
237     virtual ConstHostCollection getAllbyHostname6(const std::string& hostname,
238                                                   const SubnetID& subnet_id) const;
239 
240     /// @brief Returns range of hosts in a DHCPv4 subnet.
241     ///
242     /// This method implements paged browsing of host databases. The
243     /// parameters specify a page size, an index in sources and the
244     /// starting host id of the range. If not zero this host id is
245     /// excluded from the returned range. When a source is exhausted
246     /// the index is updated. There is no guarantee about the order
247     /// of returned host reservations, only the sources and
248     /// reservations from the same source are ordered.
249     ///
250     /// @param subnet_id Subnet identifier.
251     /// @param source_index Index of the source (unused).
252     /// @param lower_host_id Host identifier used as lower bound for the
253     /// returned range.
254     /// @param page_size maximum size of the page returned.
255     ///
256     /// @return Collection of const @c Host objects (may be empty).
257     virtual ConstHostCollection getPage4(const SubnetID& subnet_id,
258                                          size_t& source_index,
259                                          uint64_t lower_host_id,
260                                          const HostPageSize& page_size) const;
261 
262     /// @brief Returns range of hosts in a DHCPv6 subnet.
263     ///
264     /// This method implements paged browsing of host databases. The
265     /// parameters specify a page size, an index in sources and the
266     /// starting host id of the range. If not zero this host id is
267     /// excluded from the returned range. When a source is exhausted
268     /// the index is updated. There is no guarantee about the order
269     /// of returned host reservations, only the sources and
270     /// reservations from the same source are ordered.
271     ///
272     /// @param subnet_id Subnet identifier.
273     /// @param source_index Index of the source (unused).
274     /// @param lower_host_id Host identifier used as lower bound for the
275     /// returned range.
276     /// @param page_size maximum size of the page returned.
277     ///
278     /// @return Collection of const @c Host objects (may be empty).
279     virtual ConstHostCollection getPage6(const SubnetID& subnet_id,
280                                          size_t& source_index,
281                                          uint64_t lower_host_id,
282                                          const HostPageSize& page_size) const;
283 
284     /// @brief Returns range of hosts.
285     ///
286     /// This method implements paged browsing of host databases. The
287     /// parameters specify a page size, an index in sources and the
288     /// starting host id of the range. If not zero this host id is
289     /// excluded from the returned range. When a source is exhausted
290     /// the index is updated. There is no guarantee about the order
291     /// of returned host reservations, only the sources and
292     /// reservations from the same source are ordered.
293     ///
294     /// @param source_index Index of the source (unused).
295     /// @param lower_host_id Host identifier used as lower bound for the
296     /// returned range.
297     /// @param page_size maximum size of the page returned.
298     ///
299     /// @return Collection of const @c Host objects (may be empty).
300     virtual ConstHostCollection getPage4(size_t& source_index,
301                                          uint64_t lower_host_id,
302                                          const HostPageSize& page_size) const;
303 
304     /// @brief Returns range of hosts.
305     ///
306     /// This method implements paged browsing of host databases. The
307     /// parameters specify a page size, an index in sources and the
308     /// starting host id of the range. If not zero this host id is
309     /// excluded from the returned range. When a source is exhausted
310     /// the index is updated. There is no guarantee about the order
311     /// of returned host reservations, only the sources and
312     /// reservations from the same source are ordered.
313     ///
314     /// @param source_index Index of the source (unused).
315     /// @param lower_host_id Host identifier used as lower bound for the
316     /// returned range.
317     /// @param page_size maximum size of the page returned.
318     ///
319     /// @return Collection of const @c Host objects (may be empty).
320     virtual ConstHostCollection getPage6(size_t& source_index,
321                                          uint64_t lower_host_id,
322                                          const HostPageSize& page_size) const;
323 
324     /// @brief Returns a collection of hosts using the specified IPv4 address.
325     ///
326     /// This method may return multiple @c Host objects if they are connected
327     /// to different subnets.
328     ///
329     /// @param address IPv4 address for which the @c Host object is searched.
330     ///
331     /// @return Collection of const @c Host objects.
332     virtual ConstHostCollection getAll4(const asiolink::IOAddress& address) const;
333 
334     /// @brief Returns a host connected to the IPv4 subnet.
335     ///
336     /// @param subnet_id Subnet identifier.
337     /// @param identifier_type Identifier type.
338     /// @param identifier_begin Pointer to a beginning of a buffer containing
339     /// an identifier.
340     /// @param identifier_len Identifier length.
341     ///
342     /// @return Const @c Host object for which reservation has been made using
343     /// the specified identifier.
344     virtual ConstHostPtr get4(const SubnetID& subnet_id,
345                               const Host::IdentifierType& identifier_type,
346                               const uint8_t* identifier_begin,
347                               const size_t identifier_len) const;
348 
349     /// @brief Returns a host connected to the IPv4 subnet and having
350     /// a reservation for a specified IPv4 address.
351     ///
352     /// One of the use cases for this method is to detect collisions between
353     /// dynamically allocated addresses and reserved addresses. When the new
354     /// address is assigned to a client, the allocation mechanism should check
355     /// if this address is not reserved for some other host and do not allocate
356     /// this address if reservation is present.
357     ///
358     /// @param subnet_id Subnet identifier.
359     /// @param address reserved IPv4 address.
360     ///
361     /// @return Const @c Host object using a specified IPv4 address.
362     /// @throw BadValue is given an IPv6 address
363     virtual ConstHostPtr get4(const SubnetID& subnet_id,
364                               const asiolink::IOAddress& address) const;
365 
366     /// @brief Returns all hosts connected to the IPv4 subnet and having
367     /// a reservation for a specified address.
368     ///
369     /// In most cases it is desired that there is at most one reservation
370     /// for a given IPv4 address within a subnet. In a default configuration,
371     /// the backend does not allow for inserting more than one host with
372     /// the same IPv4 reservation. In that case, the number of hosts returned
373     /// by this function is 0 or 1.
374     ///
375     /// If the backend is configured to allow multiple hosts with reservations
376     /// for the same IPv4 address in the given subnet, this method can return
377     /// more than one host.
378     ///
379     /// The typical use case when a single IPv4 address is reserved for multiple
380     /// hosts is when these hosts represent different interfaces of the same
381     /// machine and each interface comes with a different MAC address. In that
382     /// case, the same IPv4 address is assigned regardless of which interface is
383     /// used by the DHCP client to communicate with the server.
384     ///
385     /// @param subnet_id Subnet identifier.
386     /// @param address reserved IPv4 address.
387     ///
388     /// @return Collection of const @c Host objects.
389     virtual ConstHostCollection
390     getAll4(const SubnetID& subnet_id,
391             const asiolink::IOAddress& address) const;
392 
393     /// @brief Returns a host connected to the IPv6 subnet.
394     ///
395     /// @param subnet_id Subnet identifier.
396     /// @param identifier_type Identifier type.
397     /// @param identifier_begin Pointer to a beginning of a buffer containing
398     /// an identifier.
399     /// @param identifier_len Identifier length.
400     ///
401     /// @return Const @c Host object for which reservation has been made using
402     /// the specified identifier.
403     virtual ConstHostPtr get6(const SubnetID& subnet_id,
404                               const Host::IdentifierType& identifier_type,
405                               const uint8_t* identifier_begin,
406                               const size_t identifier_len) const;
407 
408     /// @brief Returns a host using the specified IPv6 prefix.
409     ///
410     /// @param prefix IPv6 prefix for which the @c Host object is searched.
411     /// @param prefix_len IPv6 prefix length.
412     ///
413     /// @return Const @c Host object using a specified IPv6 prefix.
414     virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
415                               const uint8_t prefix_len) const;
416 
417     /// @brief Returns a host connected to the IPv6 subnet and having
418     /// a reservation for a specified IPv6 address or prefix.
419     ///
420     /// @param subnet_id Subnet identifier.
421     /// @param address reserved IPv6 address/prefix.
422     ///
423     /// @return Const @c Host object using a specified IPv6 address/prefix.
424     virtual ConstHostPtr get6(const SubnetID& subnet_id,
425                               const asiolink::IOAddress& address) const;
426 
427     /// @brief Returns all hosts connected to the IPv6 subnet and having
428     /// a reservation for a specified address or delegated prefix (lease).
429     ///
430     /// In most cases it is desired that there is at most one reservation
431     /// for a given IPv6 lease within a subnet. In a default configuration,
432     /// the backend does not allow for inserting more than one host with
433     /// the same IPv6 address or prefix. In that case, the number of hosts
434     /// returned by this function is 0 or 1.
435     ///
436     /// If the backend is configured to allow multiple hosts with reservations
437     /// for the same IPv6 lease in the given subnet, this method can return
438     /// more than one host.
439     ///
440     /// The typical use case when a single IPv6 lease is reserved for multiple
441     /// hosts is when these hosts represent different interfaces of the same
442     /// machine and each interface comes with a different MAC address. In that
443     /// case, the same IPv6 lease is assigned regardless of which interface is
444     /// used by the DHCP client to communicate with the server.
445     ///
446     /// @param subnet_id Subnet identifier.
447     /// @param address reserved IPv6 address/prefix.
448     ///
449     /// @return Collection of const @c Host objects.
450     virtual ConstHostCollection
451     getAll6(const SubnetID& subnet_id,
452             const asiolink::IOAddress& address) const;
453 
454     /// @brief Return backend type
455     ///
456     /// Returns the type of database as the string "postgresql".  This is
457     /// same value as used for configuration purposes.
458     ///
459     /// @return Type of the backend.
getType()460     virtual std::string getType() const {
461         return (std::string("postgresql"));
462     }
463 
464     /// @brief Returns the name of the open database
465     ///
466     /// @return String containing the name of the database
467     virtual std::string getName() const;
468 
469     /// @brief Returns description of the backend.
470     ///
471     /// This description may be multiline text that describes the backend.
472     ///
473     /// @return Description of the backend.
474     virtual std::string getDescription() const;
475 
476     /// @brief Returns backend version.
477     ///
478     /// The method is called by the constructor after opening the database
479     /// but prior to preparing SQL statements, to verify that the schema version
480     /// is correct. Thus it must not rely on a pre-prepared statement or
481     /// formal statement execution error checking.
482     ///
483     /// @return Version number stored in the database, as a pair of unsigned
484     ///         integers. "first" is the major version number, "second" the
485     ///         minor number.
486     ///
487     /// @throw isc::db::DbOperationError An operation on the open database
488     ///        has failed.
489     virtual std::pair<uint32_t, uint32_t> getVersion() const;
490 
491     /// @brief Commit Transactions
492     ///
493     /// Commits all pending database operations.
494     virtual void commit();
495 
496     /// @brief Rollback Transactions
497     ///
498     /// Rolls back all pending database operations.
499     virtual void rollback();
500 
501     /// @brief Controls whether IP reservations are unique or non-unique.
502     ///
503     /// In a typical case, the IP reservations are unique and backends verify
504     /// prior to adding a host reservation to the database that the reservation
505     /// for a given IP address/subnet does not exist. In some cases it may be
506     /// required to allow non-unique IP reservations, e.g. in the case when a
507     /// host has several interfaces and independently of which interface is used
508     /// by this host to communicate with the DHCP server the same IP address
509     /// should be assigned. In this case the @c unique value should be set to
510     /// false to disable the checks for uniqueness on the backend side.
511     ///
512     /// @param unique boolean flag indicating if the IP reservations must be
513     /// unique within the subnet or can be non-unique.
514     /// @return always true because this backend supports both the case when
515     /// the addresses must be unique and when they may be non-unique.
516     virtual bool setIPReservationsUnique(const bool unique);
517 
518     /// @brief Flag which indicates if the host manager has at least one
519     /// unusable connection.
520     ///
521     /// @return true if there is at least one unusable connection, false
522     /// otherwise
523     virtual bool isUnusable();
524 
525     /// @brief Context RAII Allocator.
526     class PgSqlHostContextAlloc {
527     public:
528 
529         /// @brief Constructor
530         ///
531         /// This constructor takes a context of the pool if one is available
532         /// or creates a new one.
533         ///
534         /// @param mgr A parent instance
535         PgSqlHostContextAlloc(PgSqlHostDataSourceImpl& mgr);
536 
537         /// @brief Destructor
538         ///
539         /// This destructor puts back the context in the pool.
540         ~PgSqlHostContextAlloc();
541 
542         /// @brief The context
543         PgSqlHostContextPtr ctx_;
544 
545     private:
546         /// @brief The manager
547         PgSqlHostDataSourceImpl& mgr_;
548     };
549 
550 private:
551     /// @brief Pointer to the implementation of the @ref PgSqlHostDataSource.
552     PgSqlHostDataSourceImplPtr impl_;
553 };
554 
555 }
556 }
557 
558 #endif // PGSQL_HOST_DATA_SOURCE_H
559