From 403663bb87377a70e1b90862f1675190dd4c731f Mon Sep 17 00:00:00 2001 From: Thomas Markwalder Date: Tue, 20 May 2014 16:01:25 -0400 Subject: [PATCH] [3382] Addressed review comments Added tests for Y2038 timestamps. Added missing commentary. Minor cleanup. --- src/lib/dhcpsrv/database_backends.dox | 32 +++++++++---- src/lib/dhcpsrv/lease_mgr.cc | 2 + src/lib/dhcpsrv/lease_mgr.h | 4 ++ src/lib/dhcpsrv/pgsql_lease_mgr.cc | 42 +++++++++++------ src/lib/dhcpsrv/pgsql_lease_mgr.h | 40 +++++++++++----- .../tests/generic_lease_mgr_unittest.cc | 47 ++++++++++++++++++- .../tests/generic_lease_mgr_unittest.h | 7 +++ .../dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 10 ++++ .../dhcpsrv/tests/pgsql_lease_mgr_unittest.cc | 10 ++++ 9 files changed, 159 insertions(+), 35 deletions(-) diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox index 11ee9f7203..556b6a83bf 100644 --- a/src/lib/dhcpsrv/database_backends.dox +++ b/src/lib/dhcpsrv/database_backends.dox @@ -22,16 +22,22 @@ the abstract isc::dhcp::LeaseMgr class. This provides methods to create, retrieve, modify and delete leases in the database. - There are currently two available Lease Managers, MySQL and Memfile: + There are currently three available Lease Managers, Memfile, MySQL and + PostgreSQL: + + - Memfile is an in-memory lease database which can be configured to persist + its content to disk in a flat-file. Support for the Memfile database + backend is built into Kea DHCP. - The MySQL lease manager uses the freely available MySQL as its backend - database. This is not included in BIND 10 DHCP by default: + database. This is not included in Kea DHCP by default: the \--with-dhcp-mysql switch must be supplied to "configure" for support to be compiled into the software. - - Memfile is an in-memory lease database, with (currently) nothing being - written to persistent storage. The long-term plans for the backend do - include the ability to store this on disk, but it is currently a - low-priority item. + + - The PostgreSQL lease manager uses the freely available PostgreSQL as its + backend database. This is not included in Kea DHCP by default: + the \--with-dhcp-pgsql switch must be supplied to "configure" for + support to be compiled into the software. @section dhcpdb-instantiation Instantiation of Lease Managers @@ -74,6 +80,16 @@ - user - database user ID under which the database is accessed. If not specified, no user ID is used - the database is assumed to be open. + @subsection dhcpdb-keywords-pgsql PostgreSQL connection string keywords + + - host - host on which the selected database is running. If not + supplied, "localhost" is assumed. + - name - name of the PostgreSQL database to access. There is no + default - this must always be supplied. + - password - password for the selected user ID (see below). If not + specified, no password is used. + - user - database user ID under which the database is accessed. If not + specified, no user ID is used - the database is assumed to be open. @section dhcp-backend-unittest Running Unit Tests @@ -81,7 +97,7 @@ certain database-specific pre-requisites for successfully running the unit tests. These are listed in the following sections. - @subsection dhcp-mysql-unittest MySQL + @subsection dhcp-mysql-unittest MySQL Unit Tests A database called keatest must be created. A database user, also called keatest (and with a password keatest) must also be created and @@ -122,7 +138,7 @@ that BIND 10 has been build with the \--with-dhcp-mysql switch (see the installation section in the BIND 10 Guide). - @subsection dhcp-pgsql-unittest PostgreSQL unit-tests + @subsection dhcp-pgsql-unittest PostgreSQL Unit Tests Conceptually, the steps required to run PostgreSQL unit-tests are the same as in MySQL. First, a database called keatest must be created. A database diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc index e4fa07f2b9..6a8f358358 100644 --- a/src/lib/dhcpsrv/lease_mgr.cc +++ b/src/lib/dhcpsrv/lease_mgr.cc @@ -32,6 +32,8 @@ using namespace std; namespace isc { namespace dhcp { +const time_t LeaseMgr::MAX_DB_TIME = 2147483647; + std::string LeaseMgr::getParameter(const std::string& name) const { ParameterMap::const_iterator param = parameters_.find(name); if (param == parameters_.end()) { diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index aab7b9ed05..001fef34da 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -122,6 +122,10 @@ public: /// see the documentation of those classes for details. class LeaseMgr { public: + /// @brief Defines maxiumum value for time that can be reliably stored. + // If I'm still alive I'll be too old to care. You fix it. + static const time_t MAX_DB_TIME; + /// Database configuration parameter map typedef std::map ParameterMap; diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc index f84f517dbf..eef68d5a87 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.cc +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc @@ -268,6 +268,7 @@ std::string PsqlBindArray::toText() { class PgSqlLeaseExchange { public: + PgSqlLeaseExchange() : addr_str_(""), valid_lifetime_(0), valid_lft_str_(""), expire_(0), expire_str_(""), subnet_id_(0), subnet_id_str_(""), @@ -289,6 +290,14 @@ public: /// @return std::string containing the stringified time std::string convertToDatabaseTime(const time_t& time_val) { + // PostgreSQL does funny things with time if you get past Y2038. It + // will accept the values (unlike MySQL which throws) but it + // stops correctly adjusting to local time when reading them back + // out. So lets disallow it here. + if (time_val > LeaseMgr::MAX_DB_TIME) { + isc_throw(BadValue, "Time value is too large: " << time_val); + } + struct tm tinfo; char buffer[20]; localtime_r(&time_val, &tinfo); @@ -326,10 +335,11 @@ public: /// /// @return a const char* pointer to the column's raw data /// @throw DbOperationError if the value cannot be fetched. - const char* getColumnValue(PGresult*& r, const int row, const size_t col) { + const char* getRawColumnValue(PGresult*& r, const int row, + const size_t col) { const char* value = PQgetvalue(r, row, col); if (!value) { - isc_throw(DbOperationError, "getColumnValue no data for :" + isc_throw(DbOperationError, "getRawColumnValue no data for :" << getColumnLabel(col) << " row:" << row); } @@ -347,7 +357,7 @@ public: /// invalid. void getColumnValue(PGresult*& r, const int row, const size_t col, bool &value) { - const char* data = getColumnValue(r, row, col); + const char* data = getRawColumnValue(r, row, col); if (!strlen(data) || *data == 'f') { value = false; } else if (*data == 't') { @@ -370,7 +380,7 @@ public: /// invalid. void getColumnValue(PGresult*& r, const int row, const size_t col, uint32_t &value) { - const char* data = getColumnValue(r, row, col); + const char* data = getRawColumnValue(r, row, col); try { value = boost::lexical_cast(data); } catch (const std::exception& ex) { @@ -391,7 +401,7 @@ public: /// invalid. void getColumnValue(PGresult*& r, const int row, const size_t col, uint8_t &value) { - const char* data = getColumnValue(r, row, col); + const char* data = getRawColumnValue(r, row, col); try { // lexically casting as uint8_t doesn't convert from char // so we use uint16_t and implicitly convert. @@ -458,7 +468,7 @@ public: // Returns converted bytes in a dynamically allocated buffer, and // sets bytes_converted. unsigned char* bytes = PQunescapeBytea((const unsigned char*) - (getColumnValue(r, row, col)), + (getRawColumnValue(r, row, col)), &bytes_converted); // Unlikely it couldn't allocate it but you never know. @@ -649,8 +659,8 @@ public: getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_); - expire_ = convertFromDatabaseTime(getColumnValue(r, row, - EXPIRE_COL)); + expire_ = convertFromDatabaseTime(getRawColumnValue(r, row, + EXPIRE_COL)); getColumnValue(r, row , SUBNET_ID_COL, subnet_id_); @@ -659,7 +669,7 @@ public: getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_); getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_); - hostname_ = getColumnValue(r, row, HOSTNAME_COL); + hostname_ = getRawColumnValue(r, row, HOSTNAME_COL); return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_, client_id_buffer_, client_id_length_, @@ -761,7 +771,11 @@ public: addr_str_ = lease_->addr_.toText(); bind_array.add(addr_str_); - bind_array.add(lease_->duid_->getDuid()); + if (lease_->duid_) { + bind_array.add(lease_->duid_->getDuid()); + } else { + isc_throw (BadValue, "IPv6 Lease cannot have a null DUID"); + } valid_lft_str_ = boost::lexical_cast (lease->valid_lft_); @@ -819,8 +833,8 @@ public: getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_); - expire_ = convertFromDatabaseTime(getColumnValue(r, row, - EXPIRE_COL)); + expire_ = convertFromDatabaseTime(getRawColumnValue(r, row, + EXPIRE_COL)); cltt_ = expire_ - valid_lifetime_; @@ -837,7 +851,7 @@ public: getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_); getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_); - hostname_ = getColumnValue(r, row, HOSTNAME_COL); + hostname_ = getRawColumnValue(r, row, HOSTNAME_COL); Lease6Ptr result(new Lease6(lease_type_, addr, duid_ptr, iaid_, pref_lifetime_, valid_lifetime_, 0, 0, @@ -863,7 +877,7 @@ public: /// invalid. isc::asiolink::IOAddress getIPv6Value(PGresult*& r, const int row, const size_t col) { - const char* data = getColumnValue(r, row, col); + const char* data = getRawColumnValue(r, row, col); try { return (isc::asiolink::IOAddress(data)); } catch (const std::exception& ex) { diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h index db46b84461..86c1e9b6a5 100644 --- a/src/lib/dhcpsrv/pgsql_lease_mgr.h +++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h @@ -28,7 +28,7 @@ namespace isc { namespace dhcp { /// @brief Structure used to bind C++ input values to dynamic SQL parameters -/// The structure contains three vectors which storing the input values, +/// The structure contains three vectors which store the input values, /// data lengths, and formats. These vectors are passed directly into the /// PostgreSQL execute call. /// @@ -69,19 +69,37 @@ struct PsqlBindArray { return (values_.empty()); } - /// @brief Adds a char value to the array. + /// @brief Adds a char array to bind array based + /// + /// Adds a TEXT_FMT value to the end of the bind array, using the given + /// char* as the data source. Note that value is expected to be NULL + /// terminated. + /// + /// @param value char array containing the null-terminated text to add. void add(const char* value); - /// @brief Adds a string value to the array. + /// @brief Adds an string value to the bind array + /// + /// Adds a TEXT formatted value to the end of the bind array using the + /// given string as the data source. + /// + /// @param value std::string containing the value to add. void add(const std::string& value); - /// @brief Adds a vector to the array. + /// @brief Adds a binary value to the bind array. + /// + /// Adds a BINARY_FMT value to the end of the bind array using the + /// given vector as the data source. + /// + /// @param value vector of binary bytes. void add(const std::vector& data); - /// @brief Adds a uint32_t value to the array. - void add(const uint32_t& value); - - /// @brief Adds a bool value to the array. + /// @brief Adds a boolean value to the bind array. + /// + /// Converts the given boolean value to its corresponding to PostgreSQL + /// string value and adds it as a TEXT_FMT value to the bind array. + /// + /// @param value std::string containing the value to add. void add(const bool& value); /// @brief Dumps the contents of the array to a string. @@ -370,16 +388,14 @@ public: /// @brief Commit Transactions /// - /// Commits all pending database operations. On databases that don't - /// support transactions, this is a no-op. + /// Commits all pending database operations. /// /// @throw DbOperationError Iif the commit failed. virtual void commit(); /// @brief Rollback Transactions /// - /// Rolls back all pending database operations. On databases that don't - /// support transactions, this is a no-op. + /// Rolls back all pending database operations. /// /// @throw DbOperationError If the rollback failed. virtual void rollback(); diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc index 70fb327867..e9bde2465c 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc @@ -682,6 +682,29 @@ GenericLeaseMgrTest::testAddGetDelete6(bool check_t1_t2) { EXPECT_FALSE(x); } +void +GenericLeaseMgrTest::testMaxDate4() { + // Get the leases to be used for the test. + vector leases = createLeases4(); + Lease4Ptr lease = leases[1]; + + // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire + // time too large to store. + lease->valid_lft_ = 24*60*60; + lease->cltt_ = LeaseMgr::MAX_DB_TIME; + + // Insert should throw. + ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); + + // Set valid_lft_ to 0, which should make expire time small enough to + // store and try again. + lease->valid_lft_ = 0; + EXPECT_TRUE(lmptr_->addLease(leases[1])); + Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]); + ASSERT_TRUE(l_returned); + detailCompareLease(leases[1], l_returned); +} + void GenericLeaseMgrTest::testBasicLease4() { // Get the leases to be used for the test. @@ -710,7 +733,6 @@ GenericLeaseMgrTest::testBasicLease4() { detailCompareLease(leases[3], l_returned); // Check that we can't add a second lease with the same address -std::cout << "OK - Duplicate check here!!!!!!" << std::endl; EXPECT_FALSE(lmptr_->addLease(leases[1])); // Delete a lease, check that it's gone, and that we can't delete it @@ -829,6 +851,29 @@ GenericLeaseMgrTest::testBasicLease6() { detailCompareLease(leases[2], l_returned); } +void +GenericLeaseMgrTest::testMaxDate6() { + // Get the leases to be used for the test. + vector leases = createLeases6(); + Lease6Ptr lease = leases[1]; + + // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire + // time too large to store. + lease->valid_lft_ = 24*60*60; + lease->cltt_ = LeaseMgr::MAX_DB_TIME; + + // Insert should throw. + ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError); + + // Set valid_lft_ to 0, which should make expire time small enough to + // store and try again. + lease->valid_lft_ = 0; + EXPECT_TRUE(lmptr_->addLease(leases[1])); + Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]); + ASSERT_TRUE(l_returned); + detailCompareLease(leases[1], l_returned); +} + void GenericLeaseMgrTest::testLease4InvalidHostname() { // Get the leases to be used for the test. diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h index c153f08237..9058f54039 100644 --- a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h +++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h @@ -105,6 +105,9 @@ public: /// @brief checks that addLease, getLease4(addr) and deleteLease() works void testBasicLease4(); + /// @brief checks that invalid dates are safely handled. + void testMaxDate4(); + /// @brief Test lease retrieval using client id. void testGetLease4ClientId(); @@ -180,6 +183,10 @@ public: /// IPv6 address) works. void testBasicLease6(); + /// @brief checks that invalid dates are safely handled. + void testMaxDate6(); + + /// @brief Test that IPv6 lease can be added, retrieved and deleted. /// /// This method checks basic IPv6 lease operations. There's check_t1_t2 diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc index b9854518e0..fecf32c33e 100644 --- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc @@ -333,6 +333,11 @@ TEST_F(MySqlLeaseMgrTest, basicLease4) { testBasicLease4(); } +/// @brief Check that Lease4 code safely handles invalid dates. +TEST_F(MySqlLeaseMgrTest, maxDate4) { + testMaxDate4(); +} + /// @brief Lease4 update tests /// /// Checks that we are able to update a lease in the database. @@ -437,6 +442,11 @@ TEST_F(MySqlLeaseMgrTest, basicLease6) { testBasicLease6(); } +/// @brief Check that Lease6 code safely handles invalid dates. +TEST_F(MySqlLeaseMgrTest, maxDate6) { + testMaxDate6(); +} + /// @brief Verify that too long hostname for Lease6 is not accepted. /// /// Checks that the it is not possible to create a lease when the hostname diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc index 649ae9af9d..d3eb6ff2f4 100644 --- a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc @@ -299,6 +299,11 @@ TEST_F(PgSqlLeaseMgrTest, basicLease4) { testBasicLease4(); } +/// @brief Check that Lease4 code safely handles invalid dates. +TEST_F(PgSqlLeaseMgrTest, maxDate4) { + testMaxDate4(); +} + /// @brief Lease4 update tests /// /// Checks that we are able to update a lease in the database. @@ -403,6 +408,11 @@ TEST_F(PgSqlLeaseMgrTest, basicLease6) { testBasicLease6(); } +/// @brief Check that Lease6 code safely handles invalid dates. +TEST_F(PgSqlLeaseMgrTest, maxDate6) { + testMaxDate6(); +} + /// @brief Verify that too long hostname for Lease6 is not accepted. /// /// Checks that the it is not possible to create a lease when the hostname -- GitLab