summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMukund Sivaraman <muks@isc.org>2014-02-06 10:01:17 +0100
committerMukund Sivaraman <muks@isc.org>2014-02-06 10:01:17 +0100
commit8b413f0b6f0325a124af6e170e9c96016d032007 (patch)
treee091a297313fbd71ab7fd76472cb9bd2d50c088e
parent[2168] Fix more unittests (diff)
parentMerge branch 'trac956' (diff)
downloadkea-8b413f0b6f0325a124af6e170e9c96016d032007.tar.xz
kea-8b413f0b6f0325a124af6e170e9c96016d032007.zip
Merge branch 'master' into trac2168
Conflicts: src/lib/dns/tests/rrset_unittest.cc
-rw-r--r--ChangeLog116
-rw-r--r--configure.ac5
-rw-r--r--doc/devel/contribute.dox4
-rw-r--r--doc/devel/mainpage.dox10
-rw-r--r--doc/guide/bind10-guide.xml106
-rw-r--r--src/bin/auth/.gitignore2
-rw-r--r--src/bin/auth/auth_messages.mes6
-rw-r--r--src/bin/auth/auth_srv.cc31
-rw-r--r--src/bin/auth/auth_srv.h2
-rw-r--r--src/bin/auth/query.cc25
-rw-r--r--src/bin/auth/query.h12
-rw-r--r--src/bin/auth/tests/query_unittest.cc7
-rw-r--r--src/bin/bindctl/bindcmd.py5
-rw-r--r--src/bin/d2/.gitignore1
-rw-r--r--src/bin/d2/d2_cfg_mgr.cc5
-rw-r--r--src/bin/d2/dns_client.h10
-rw-r--r--src/bin/d2/tests/d2_update_mgr_unittests.cc8
-rw-r--r--src/bin/d2/tests/dns_client_unittests.cc24
-rw-r--r--src/bin/d2/tests/nc_add_unittests.cc117
-rw-r--r--src/bin/d2/tests/nc_test_utils.cc13
-rw-r--r--src/bin/d2/tests/nc_trans_unittests.cc181
-rw-r--r--src/bin/d2/tests/test_data_files_config.h.in2
-rwxr-xr-xsrc/bin/ddns/ddns.py.in5
-rwxr-xr-xsrc/bin/ddns/tests/ddns_test.py5
-rw-r--r--src/bin/dhcp4/.gitignore1
-rw-r--r--src/bin/dhcp4/config_parser.cc16
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.cc9
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.h3
-rw-r--r--src/bin/dhcp4/dhcp4.dox30
-rw-r--r--src/bin/dhcp4/dhcp4.spec91
-rw-r--r--src/bin/dhcp4/dhcp4_messages.mes17
-rw-r--r--src/bin/dhcp4/dhcp4_srv.cc373
-rw-r--r--src/bin/dhcp4/dhcp4_srv.h36
-rw-r--r--src/bin/dhcp4/tests/.gitignore3
-rw-r--r--src/bin/dhcp4/tests/Makefile.am3
-rw-r--r--src/bin/dhcp4/tests/config_parser_unittest.cc534
-rw-r--r--src/bin/dhcp4/tests/dhcp4_srv_unittest.cc122
-rw-r--r--src/bin/dhcp4/tests/dhcp4_test_utils.cc8
-rw-r--r--src/bin/dhcp4/tests/dhcp4_test_utils.h12
-rw-r--r--src/bin/dhcp4/tests/fqdn_unittest.cc356
-rw-r--r--src/bin/dhcp4/tests/test_data_files_config.h.in17
-rw-r--r--src/bin/dhcp4/tests/wireshark.cc60
-rw-r--r--src/bin/dhcp6/.gitignore1
-rw-r--r--src/bin/dhcp6/config_parser.cc8
-rw-r--r--src/bin/dhcp6/ctrl_dhcp6_srv.cc11
-rw-r--r--src/bin/dhcp6/ctrl_dhcp6_srv.h3
-rw-r--r--src/bin/dhcp6/dhcp6.dox27
-rw-r--r--src/bin/dhcp6/dhcp6_messages.mes23
-rw-r--r--src/bin/dhcp6/dhcp6_srv.cc383
-rw-r--r--src/bin/dhcp6/dhcp6_srv.h127
-rw-r--r--src/bin/dhcp6/tests/.gitignore3
-rw-r--r--src/bin/dhcp6/tests/Makefile.am3
-rw-r--r--src/bin/dhcp6/tests/config_parser_unittest.cc419
-rw-r--r--src/bin/dhcp6/tests/dhcp6_srv_unittest.cc91
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.cc8
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.h5
-rw-r--r--src/bin/dhcp6/tests/fqdn_unittest.cc262
-rw-r--r--src/bin/dhcp6/tests/wireshark.cc150
-rw-r--r--src/bin/resolver/.gitignore1
-rw-r--r--src/bin/xfrin/tests/xfrin_test.py5
-rwxr-xr-xsrc/bin/xfrin/xfrin.py.in3
-rw-r--r--src/bin/xfrin/xfrin_messages.mes6
-rwxr-xr-xsrc/bin/xfrout/xfrout.py.in13
-rw-r--r--src/hooks/dhcp/user_chk/Makefile.am3
-rw-r--r--src/hooks/dhcp/user_chk/tests/.gitignore2
-rw-r--r--src/lib/acl/ip_check.cc2
-rw-r--r--src/lib/asiodns/.gitignore1
-rw-r--r--src/lib/asiodns/io_fetch.cc2
-rw-r--r--src/lib/asiodns/tests/io_fetch_unittest.cc8
-rw-r--r--src/lib/asiolink/dummy_io_cb.h32
-rw-r--r--src/lib/asiolink/io_address.cc27
-rw-r--r--src/lib/asiolink/io_address.h38
-rw-r--r--src/lib/asiolink/io_endpoint.cc4
-rw-r--r--src/lib/asiolink/tcp_socket.h2
-rw-r--r--src/lib/asiolink/tests/Makefile.am1
-rw-r--r--src/lib/asiolink/tests/dummy_io_callback_unittest.cc36
-rw-r--r--src/lib/asiolink/tests/io_address_unittest.cc47
-rw-r--r--src/lib/asiolink/tests/tcp_socket_unittest.cc8
-rw-r--r--src/lib/asiolink/tests/udp_socket_unittest.cc2
-rw-r--r--src/lib/cache/.gitignore1
-rw-r--r--src/lib/cc/.gitignore1
-rw-r--r--src/lib/cc/session.h2
-rw-r--r--src/lib/cc/tests/data_unittests.cc12
-rw-r--r--src/lib/config/.gitignore1
-rw-r--r--src/lib/config/config_data.h4
-rw-r--r--src/lib/config/tests/testdata/spec22.spec2
-rw-r--r--src/lib/config/tests/testdata/spec27.spec2
-rw-r--r--src/lib/datasrc/.gitignore2
-rw-r--r--src/lib/datasrc/cache_config.cc6
-rw-r--r--src/lib/datasrc/cache_config.h2
-rw-r--r--src/lib/datasrc/client_list.h2
-rw-r--r--src/lib/datasrc/database.cc12
-rw-r--r--src/lib/datasrc/memory/.gitignore1
-rw-r--r--src/lib/datasrc/memory/domaintree.h4
-rw-r--r--src/lib/datasrc/memory/memory_client.cc15
-rw-r--r--src/lib/datasrc/memory/rdataset.h2
-rw-r--r--src/lib/datasrc/memory/treenode_rrset.cc80
-rw-r--r--src/lib/datasrc/memory/treenode_rrset.h9
-rw-r--r--src/lib/datasrc/memory/zone_data_loader.cc2
-rw-r--r--src/lib/datasrc/memory/zone_data_updater.h2
-rw-r--r--src/lib/datasrc/memory/zone_table.h2
-rw-r--r--src/lib/datasrc/memory/zone_writer_local.h95
-rw-r--r--src/lib/datasrc/sqlite3_accessor.cc1
-rw-r--r--src/lib/datasrc/tests/client_list_unittest.cc14
-rw-r--r--src/lib/datasrc/tests/database_unittest.cc16
-rw-r--r--src/lib/datasrc/tests/master_loader_callbacks_test.cc2
-rw-r--r--src/lib/datasrc/tests/memory/domaintree_unittest.cc2
-rw-r--r--src/lib/datasrc/tests/memory/memory_client_unittest.cc15
-rw-r--r--src/lib/datasrc/tests/memory/treenode_rrset_unittest.cc32
-rw-r--r--src/lib/datasrc/tests/mock_client.cc4
-rw-r--r--src/lib/datasrc/zone_finder.cc2
-rw-r--r--src/lib/datasrc/zone_finder.h6
-rw-r--r--src/lib/dhcp/Makefile.am1
-rw-r--r--src/lib/dhcp/docsis3_option_defs.h11
-rw-r--r--src/lib/dhcp/iface_mgr.cc255
-rw-r--r--src/lib/dhcp/iface_mgr.h86
-rw-r--r--src/lib/dhcp/iface_mgr_bsd.cc25
-rw-r--r--src/lib/dhcp/iface_mgr_error_handler.h52
-rw-r--r--src/lib/dhcp/iface_mgr_linux.cc57
-rw-r--r--src/lib/dhcp/iface_mgr_sun.cc25
-rw-r--r--src/lib/dhcp/libdhcp++.cc16
-rw-r--r--src/lib/dhcp/option.cc27
-rw-r--r--src/lib/dhcp/option4_addrlst.cc2
-rw-r--r--src/lib/dhcp/option4_client_fqdn.cc5
-rw-r--r--src/lib/dhcp/option6_addrlst.cc6
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc5
-rw-r--r--src/lib/dhcp/option6_ia.cc6
-rw-r--r--src/lib/dhcp/option6_iaaddr.cc11
-rw-r--r--src/lib/dhcp/option6_iaprefix.cc8
-rw-r--r--src/lib/dhcp/option6_iaprefix.h3
-rw-r--r--src/lib/dhcp/option_custom.cc6
-rw-r--r--src/lib/dhcp/option_data_types.h8
-rw-r--r--src/lib/dhcp/option_definition.cc72
-rw-r--r--src/lib/dhcp/option_definition.h33
-rw-r--r--src/lib/dhcp/option_int.h6
-rw-r--r--src/lib/dhcp/option_int_array.h6
-rw-r--r--src/lib/dhcp/option_vendor.cc2
-rw-r--r--src/lib/dhcp/option_vendor.h7
-rw-r--r--src/lib/dhcp/pkt4.cc14
-rw-r--r--src/lib/dhcp/pkt4.h35
-rw-r--r--src/lib/dhcp/pkt6.cc15
-rw-r--r--src/lib/dhcp/pkt6.h33
-rw-r--r--src/lib/dhcp/pkt_filter.cc6
-rw-r--r--src/lib/dhcp/pkt_filter_inet.cc3
-rw-r--r--src/lib/dhcp/std_option_defs.h5
-rw-r--r--src/lib/dhcp/tests/iface_mgr_unittest.cc422
-rw-r--r--src/lib/dhcp/tests/libdhcp++_unittest.cc46
-rw-r--r--src/lib/dhcp/tests/option4_client_fqdn_unittest.cc3
-rw-r--r--src/lib/dhcp/tests/option6_client_fqdn_unittest.cc3
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc12
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc4
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc111
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc46
-rw-r--r--src/lib/dhcp/tests/pkt6_unittest.cc31
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc4
-rw-r--r--src/lib/dhcp_ddns/.gitignore1
-rw-r--r--src/lib/dhcp_ddns/ncr_io.cc30
-rw-r--r--src/lib/dhcp_ddns/ncr_io.h29
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.cc23
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.h19
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.cc7
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_unittests.cc23
-rw-r--r--src/lib/dhcpsrv/.gitignore1
-rw-r--r--src/lib/dhcpsrv/Makefile.am2
-rw-r--r--src/lib/dhcpsrv/alloc_engine.cc87
-rw-r--r--src/lib/dhcpsrv/alloc_engine.h35
-rw-r--r--src/lib/dhcpsrv/cfgmgr.cc27
-rw-r--r--src/lib/dhcpsrv/cfgmgr.h29
-rw-r--r--src/lib/dhcpsrv/d2_client.cc263
-rw-r--r--src/lib/dhcpsrv/d2_client.h382
-rw-r--r--src/lib/dhcpsrv/dbaccess_parser.h3
-rw-r--r--src/lib/dhcpsrv/dhcp_parsers.cc133
-rw-r--r--src/lib/dhcpsrv/dhcp_parsers.h76
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_messages.mes3
-rw-r--r--src/lib/dhcpsrv/lease.cc13
-rw-r--r--src/lib/dhcpsrv/lease.h10
-rw-r--r--src/lib/dhcpsrv/lease_mgr.h2
-rw-r--r--src/lib/dhcpsrv/memfile_lease_mgr.cc4
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.cc4
-rw-r--r--src/lib/dhcpsrv/pool.cc8
-rw-r--r--src/lib/dhcpsrv/subnet.cc16
-rw-r--r--src/lib/dhcpsrv/subnet.h38
-rw-r--r--src/lib/dhcpsrv/tests/.gitignore1
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.am5
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_unittest.cc176
-rw-r--r--src/lib/dhcpsrv/tests/cfgmgr_unittest.cc48
-rw-r--r--src/lib/dhcpsrv/tests/d2_client_unittest.cc798
-rw-r--r--src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc275
-rw-r--r--src/lib/dhcpsrv/tests/lease_mgr_unittest.cc623
-rw-r--r--src/lib/dhcpsrv/tests/lease_unittest.cc694
-rw-r--r--src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc8
-rw-r--r--src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc24
-rw-r--r--src/lib/dhcpsrv/tests/test_utils.cc4
-rw-r--r--src/lib/dns/.gitignore1
-rw-r--r--src/lib/dns/Makefile.am2
-rw-r--r--src/lib/dns/labelsequence.cc17
-rw-r--r--src/lib/dns/message.cc21
-rw-r--r--src/lib/dns/message.h38
-rw-r--r--src/lib/dns/nsec3hash.h4
-rw-r--r--src/lib/dns/python/message_python.cc8
-rw-r--r--src/lib/dns/python/rdata_python.cc4
-rw-r--r--src/lib/dns/python/rrset_python.cc17
-rw-r--r--src/lib/dns/python/tests/message_python_test.py7
-rw-r--r--src/lib/dns/python/tests/rrset_python_test.py18
-rw-r--r--src/lib/dns/python/tsig_python.cc11
-rw-r--r--src/lib/dns/python/tsig_python.h6
-rw-r--r--src/lib/dns/rdata.cc9
-rw-r--r--src/lib/dns/rdata.h15
-rw-r--r--src/lib/dns/rdata/generic/sshfp_44.cc30
-rw-r--r--src/lib/dns/rdata/generic/sshfp_44.h4
-rw-r--r--src/lib/dns/rrset.cc158
-rw-r--r--src/lib/dns/rrset.h52
-rw-r--r--src/lib/dns/rrttl.cc44
-rw-r--r--src/lib/dns/tests/labelsequence_unittest.cc10
-rw-r--r--src/lib/dns/tests/message_unittest.cc45
-rw-r--r--src/lib/dns/tests/rdata_rrsig_unittest.cc49
-rw-r--r--src/lib/dns/tests/rdata_sshfp_unittest.cc32
-rw-r--r--src/lib/dns/tests/rdata_unittest.cc8
-rw-r--r--src/lib/dns/tests/rrset_unittest.cc115
-rw-r--r--src/lib/dns/tests/tsig_unittest.cc2
-rw-r--r--src/lib/hooks/.gitignore1
-rw-r--r--src/lib/hooks/tests/Makefile.am24
-rw-r--r--src/lib/log/Makefile.am2
-rw-r--r--src/lib/log/README529
-rw-r--r--src/lib/log/buffer_appender_impl.cc1
-rw-r--r--src/lib/log/interprocess/interprocess_sync_file.h2
-rw-r--r--src/lib/log/interprocess/interprocess_sync_null.h2
-rw-r--r--src/lib/log/log_formatter.h8
-rw-r--r--src/lib/log/logging.dox762
-rw-r--r--src/lib/log/message_dictionary.cc10
-rw-r--r--src/lib/log/tests/.gitignore1
-rw-r--r--src/lib/log/tests/logger_example.cc2
-rw-r--r--src/lib/log/tests/logger_manager_unittest.cc81
-rw-r--r--src/lib/nsas/.gitignore1
-rw-r--r--src/lib/python/isc/cc/tests/session_test.py8
-rw-r--r--src/lib/python/isc/config/config_data.py40
-rw-r--r--src/lib/python/isc/config/tests/config_data_test.py2
-rw-r--r--src/lib/resolve/.gitignore1
-rw-r--r--src/lib/resolve/tests/recursive_query_unittest.cc2
-rw-r--r--src/lib/resolve/tests/recursive_query_unittest_2.cc3
-rw-r--r--src/lib/resolve/tests/recursive_query_unittest_3.cc3
-rw-r--r--src/lib/server_common/.gitignore1
-rw-r--r--src/lib/testutils/srv_test.cc7
-rw-r--r--src/lib/util/io_utilities.h37
-rw-r--r--src/lib/util/tests/io_utilities_unittest.cc29
-rw-r--r--tests/lettuce/README4
-rw-r--r--tests/lettuce/configurations/.gitignore1
-rw-r--r--tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:478076
-rw-r--r--tests/lettuce/configurations/auth/auth_badzone.config.orig2
-rw-r--r--tests/lettuce/configurations/auth/auth_basic.config.orig2
-rw-r--r--tests/lettuce/configurations/bindctl/bindctl.config.orig2
-rw-r--r--tests/lettuce/configurations/bindctl_commands.config.orig2
-rw-r--r--tests/lettuce/configurations/ddns/ddns.config.orig2
-rw-r--r--tests/lettuce/configurations/ddns/noddns.config.orig2
-rw-r--r--tests/lettuce/configurations/default_user.csv1
-rw-r--r--tests/lettuce/configurations/example.org.config.orig2
-rw-r--r--tests/lettuce/configurations/example.org.inmem.config2
-rw-r--r--tests/lettuce/configurations/example2.org.config2
-rw-r--r--tests/lettuce/configurations/glue.config2
-rw-r--r--tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf2
-rw-r--r--tests/lettuce/configurations/ixfr-out/testset1-config.db4
-rw-r--r--tests/lettuce/configurations/multi_instance/multi_auth.config.orig2
-rw-r--r--tests/lettuce/configurations/no_db_file.config2
-rw-r--r--tests/lettuce/configurations/nsec3/nsec3_auth.config2
-rw-r--r--tests/lettuce/configurations/resolver/resolver_basic.config.orig2
-rw-r--r--tests/lettuce/configurations/root.config.orig40
-rw-r--r--tests/lettuce/configurations/xfrin/inmem_slave.conf2
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_master.conf.orig4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig2
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig4
-rw-r--r--tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf4
-rw-r--r--tests/lettuce/configurations/xfrout_master.conf2
-rw-r--r--tests/lettuce/data/.gitignore1
-rw-r--r--tests/lettuce/data/root.sqlite3.origbin0 -> 22528 bytes
-rw-r--r--tests/lettuce/features/ddns_system.feature6
-rw-r--r--tests/lettuce/features/example.feature18
-rw-r--r--tests/lettuce/features/inmemory_over_sqlite3.feature12
-rw-r--r--tests/lettuce/features/queries.feature40
-rw-r--r--tests/lettuce/features/terrain/bind10_control.py19
-rw-r--r--tests/lettuce/features/terrain/nsupdate.py2
-rw-r--r--tests/lettuce/features/terrain/querying.py10
-rw-r--r--tests/lettuce/features/terrain/terrain.py4
-rw-r--r--tests/lettuce/features/terrain/transfer.py14
-rw-r--r--tests/lettuce/features/xfrin_bind10.feature36
-rw-r--r--tests/lettuce/features/xfrin_notify_handling.feature106
-rw-r--r--tests/lettuce/features/xfrout_bind10.feature2
-rw-r--r--tests/tools/perfdhcp/test_control.cc16
-rw-r--r--tests/tools/perfdhcp/test_control.h7
292 files changed, 9670 insertions, 3002 deletions
diff --git a/ChangeLog b/ChangeLog
index 143fa0e44e..2a5cf1128b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,119 @@
+746. [func] tomek
+ IOAddress no longer exposes underlying asio objects. The getAddress()
+ method has been removed and replaced with several convenience methods.
+ (Trac #1485, git ecdb62db16b3f3d447db4a9d2a4079d5260431f0)
+
+745. [bug] muks
+ b10-auth now returns rcode=REFUSED for all questions with
+ qtype=RRSIG (i.e., where RRSIGs are queried directly). This is
+ because RRSIGs are meaningless without being bundled alongside the
+ RRs they cover.
+ (Trac #2226, git 68d24e65c9c3dfee38adfbe1c93367b0083f9a58)
+
+744. [func] marcin
+ b10-dhcp6: Refactored the code which is processing Client FQDN option.
+ The major user-visible change is that server generates DDNS
+ NameChangeRequest for the first IPv6 address (instead of all)
+ acquired by a client. Also, the server generates fully qualified domain
+ name from acquired IPv6 address, if the client sends an empty name in
+ Client FQDN option.
+ (Trac# 3295, git aa1c94a54114e848c64771fde308fc9ac0c00fd0)
+
+743. [func] tmark
+ b10-dhcp4 now responds with changes in DDNS behavior based upon
+ configuration parameters specified through its dhcp-ddns configuration
+ element. The parameters now supported are override-no-update,
+ override-client-update, replace-client-name, generated-prefix, and
+ qualifying-suffix.
+ (Trac# 3282, git 42b1f1e4c4f5aa48b7588233402876f5012c043c)
+
+742. [func] muks
+ The authoritative server now includes the datasource configuration
+ when logging some errors with the
+ AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR message ID.
+ (Trac #2756, git 31872754f36c840b4ec0b412a86afe9f38be86e0)
+
+741. [bug] shane
+ Remove hard-coded (and unnecessary) TSIG key from error message.
+ This also prevents a crash if the TSIG name is missing.
+ (Trac #3099, git 0ba8bbabe09756a4627e80aacdbb5050407faaac)
+
+740. [func] muks
+ When displaying messages about mismatched configuration data types
+ in entered values (between the supplied value type and expected
+ schema type), bindctl now includes both the supplied and expected
+ configuration data types in the returned error. The user has more
+ information on what caused the error now.
+ (Trac #3239, git 84d5eda2a6ae0d737aef68d56023fc33fef623e6)
+
+739. [bug] muks
+ Various minor updates were made to the SSHFP RDATA parser. Mainly,
+ the SSHFP constructor no longer throws an isc::BadValue exception.
+ generic::SSHFP::getFingerprintLen() was also renamed to
+ getFingerprintLength().
+ (Trac #3287, git 2f26d781704618c6007ba896ad3d9e0c107d04b0)
+
+738. [bug] muks
+ b10-auth now correctly processes NXDOMAIN results in the root zone
+ when using a SQLite3 data source.
+ (Trac #2951, git 13685cc4580660eaf5b041b683a2d2f31fd24de3)
+
+737. [func] muks
+ b10-auth now additionally logs the source address and port when
+ DNS messages with unsupported opcodes are received.
+ (Trac #1516, git 71611831f6d1aaaea09143d4837eddbd1d67fbf4)
+
+736. [bug] wlodek
+ b10-dhcp6 is now capable to determine if a received
+ message is addressed to it, using server identifier option.
+ The messages with non-matching server identifier are dropped.
+ (Trac #2892, git 3bd69e9b4ab9be231f7c966fd62b95a4e1595901)
+
+735. [doc] stephen
+ Expanded Developer's Guide to include chapter on logging.
+ (Trac #2566, git a08d702839d9df6cddefeccab1e7e657377145de)
+
+734. [bug] marcin
+ libdhcp++: fixed a bug which caused an error when setting boolean
+ values for an option. Also, bind10-guide has been updated with the
+ examples how to set the boolean values for an option.
+ (Trac# 3292, git 7c4c0514ede3cffc52d8c2874cdbdb74ced5f4ac)
+
+733. [bug] marcin
+ libdhcp++: a function which opens IPv6/UDPv6 sockets for the
+ DHCPv6 server, gracefully handles errors to bind socket to
+ a multicast address.
+ (Trac #3288, git 76ace0c46a5fe0e53a29dad093b817ad6c891f1b)
+
+732. [func] tomek
+ b10-dhcp4, b10-dhcp6: Support for simplified client classification
+ added. Incoming packets are now assigned to a client class based on
+ the content of the packet's user class option (DHCPv4) or vendor class
+ option (DHCPv6). Two classes (docsis3.0 and eRouter1.0) have class
+ specific behavior in b10-dhcp4. See DHCPv4 Client Classification and
+ DHCPv6 Client Classification in BIND10 Developer's Guide for details.
+ This is a first ticket in a series of planned at least three tickets.
+ (Trac #3203, git afea612c23143f81a4201e39ba793bc837c5c9f1)
+
+731. [func] tmark
+ b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
+ the DHCP-DDNS module, b10-dhcp-ddns. These parameters are part of new
+ configuration element, dhcp-ddns, defined in dhcp4.spec. The parameters
+ parse, store and retrieve but do not yet govern behavior. That will be
+ provided under separate ticket.
+ (Trac# 3033, git 0ba859834503f2b9b908cd7bc572e0286ca9201f)
+
+730. [bug] tomek
+ b10-dhcp4, b10-dhcp6: Both servers used to unnecessarily increase
+ subnet-id values after reconfiguration. The subnet-ids are now reset
+ to 1 every time a server is reconfigured.
+ (Trac #3234, git 31e416087685a6dadc3047fdbb0927bbf60095aa)
+
+729. [bug] marcin
+ b10-dhcp4 discards DHCPv4 messages carrying server identifiers
+ which don't match server identifiers used by the server.
+ (Trac #3279, git 805d2b269c6bf3e7be68c13f1da1709d8150a666)
+
728. [func] marcin
b10-dhcp6: If server fails to open a socket on one interface it
will log a warning and continue to open sockets on other interfaces.
diff --git a/configure.ac b/configure.ac
index afa9ade457..1c239a3259 100644
--- a/configure.ac
+++ b/configure.ac
@@ -76,7 +76,7 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
dnl Determine if weare using GNU sed
GNU_SED=no
-$SED --version 2> /dev/null | grep -q GNU
+$SED --version 2> /dev/null | grep GNU > /dev/null 2>&1
if test $? -eq 0; then
GNU_SED=yes
fi
@@ -445,7 +445,7 @@ fi
# Python 3.2 has an unused parameter in one of its headers. This
# has been reported, but not fixed as of yet, so we check if we need
# to set -Wno-unused-parameter.
-if test "X$GXX" = "Xyes" -a $werror_ok = 1; then
+if test "X$GXX" = "Xyes" -a "$werror_ok" = 1; then
CPPFLAGS_SAVED="$CPPFLAGS"
CPPFLAGS=${PYTHON_INCLUDES}
CXXFLAGS_SAVED="$CXXFLAGS"
@@ -1389,6 +1389,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp4/tests/Makefile
src/bin/dhcp4/tests/marker_file.h
+ src/bin/dhcp4/tests/test_data_files_config.h
src/bin/dhcp4/tests/test_libraries.h
src/bin/dhcp6/Makefile
src/bin/dhcp6/spec_config.h.pre
diff --git a/doc/devel/contribute.dox b/doc/devel/contribute.dox
index 9103bedd7d..f0ff1a6fcf 100644
--- a/doc/devel/contribute.dox
+++ b/doc/devel/contribute.dox
@@ -109,7 +109,9 @@ make distcheck
There are other useful switches which can be passed to configure. It is
always a good idea to use \c --enable-logger-checks, which does sanity
-checks on logger parameters. If you happen to modify anything in the
+checks on logger parameters. Use \c --enable-debug to enable various
+additional consistency checks that reduce performance but help during
+development. If you happen to modify anything in the
documentation, use \c --enable-generate-docs. If you are modifying DHCP
code, you are likely to be interested in enabling the MySQL backend for
DHCP. Note that if the backend is not enabled, MySQL specific unit-tests
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index c30ad35130..f31e7bfef5 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -60,6 +60,7 @@
* - @subpage dhcpv4ConfigInherit
* - @subpage dhcpv4OptionsParse
* - @subpage dhcpv4DDNSIntegration
+ * - @subpage dhcpv4Classifier
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
@@ -67,6 +68,7 @@
* - @subpage dhcpv6ConfigInherit
* - @subpage dhcpv6DDNSIntegration
* - @subpage dhcpv6OptionsParse
+ * - @subpage dhcpv6Classifier
* - @subpage dhcpv6Other
* - @subpage libdhcp
* - @subpage libdhcpIntro
@@ -84,10 +86,10 @@
* - @subpage libdhcp_ddns
*
* @section miscellaneousTopics Miscellaneous Topics
- * - @subpage LoggingApi
- * - @subpage LoggingApiOverview
- * - @subpage LoggingApiLoggerNames
- * - @subpage LoggingApiLoggingMessages
+ * - @subpage logBind10Logging
+ * - @subpage logBasicIdeas
+ * - @subpage logDeveloperUse
+ * - @subpage logNotes
* - @subpage SocketSessionUtility
* - <a href="./doxygen-error.log">Documentation warnings and errors</a>
*
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index c0197c8080..6329d25e4a 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -7,7 +7,7 @@
]>
<!--
- - Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2014 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -1554,7 +1554,10 @@ Parameters:
<term>integer</term>
<listitem>
<simpara>
- A basic integer; can be set directly with <command>config set</command>, to any integer value.
+ A basic integer; can be set directly with
+ <command>config set</command>, to any integer
+ value. The value must not be quoted, or else, it
+ will be parsed as a string.
</simpara>
</listitem>
</varlistentry>
@@ -1562,7 +1565,10 @@ Parameters:
<term>real</term>
<listitem>
<simpara>
- A basic floating point number; can be set directly with <command>config set</command>, to any floating point value.
+ A basic floating point number; can be set
+ directly with <command>config set</command>, to
+ any floating point value. The value must not be
+ quoted, or else, it will be parsed as a string.
</simpara>
</listitem>
</varlistentry>
@@ -1570,7 +1576,12 @@ Parameters:
<term>boolean</term>
<listitem>
<simpara>
- A basic boolean value; can be set directly with <command>config set</command>, to either <command>true</command> or <command>false</command>.
+ A basic boolean value; can be set directly with
+ <command>config set</command>, to either
+ <command>true</command> or
+ <command>false</command>. The value must not be
+ quoted, or else, it will be parsed as a
+ string. Integer values are not allowed.
</simpara>
</listitem>
</varlistentry>
@@ -1578,7 +1589,9 @@ Parameters:
<term>string</term>
<listitem>
<simpara>
- A basic string value; can be set directly with <command>config set,</command> so any string. Double quotation marks are optional.
+ A basic string value; can be set directly with
+ <command>config set</command> to any
+ string. Double quotation marks are optional.
</simpara>
</listitem>
</varlistentry>
@@ -3928,7 +3941,7 @@ Dhcp4/subnet4 [] list (default)
<para>
The following commands override the global
DNS servers option for a particular subnet, setting a single DNS
- server with address 2001:db8:1::3.
+ server with address 192.0.2.3.
<screen>
&gt; <userinput>config add Dhcp4/subnet4[0]/option-data</userinput>
&gt; <userinput>config set Dhcp4/subnet4[0]/option-data[0]/name "domain-name-servers"</userinput>
@@ -4172,10 +4185,10 @@ Dhcp4/subnet4 [] list (default)
primitives (uint8, string, ipv4-address etc): it is possible to
define an option comprising a number of existing primitives.
</para>
- <para>Assume we
- want to define a new option that will consist of an IPv4
- address, followed by unsigned 16 bit integer, followed by a text
- string. Such an option could be defined in the following way:
+ <para>Assume we want to define a new option that will consist of
+ an IPv4 address, followed by unsigned 16 bit integer, followed by
+ a boolean value, followed by a text string. Such an option could
+ be defined in the following way:
<screen>
&gt; <userinput>config add Dhcp4/option-def</userinput>
&gt; <userinput>config set Dhcp4/option-def[0]/name "bar"</userinput>
@@ -4183,7 +4196,7 @@ Dhcp4/subnet4 [] list (default)
&gt; <userinput>config set Dhcp4/option-def[0]/space "dhcp4"</userinput>
&gt; <userinput>config set Dhcp4/option-def[0]/type "record"</userinput>
&gt; <userinput>config set Dhcp4/option-def[0]/array false</userinput>
-&gt; <userinput>config set Dhcp4/option-def[0]/record-types "ipv4-address, uint16, string"</userinput>
+&gt; <userinput>config set Dhcp4/option-def[0]/record-types "ipv4-address, uint16, boolean, string"</userinput>
&gt; <userinput>config set Dhcp4/option-def[0]/encapsulate ""</userinput>
</screen>
The "type" is set to "record" to indicate that the option contains
@@ -4198,12 +4211,23 @@ Dhcp4/subnet4 [] list (default)
&gt; <userinput>config set Dhcp4/option-data[0]/space "dhcp4"</userinput>
&gt; <userinput>config set Dhcp4/option-data[0]/code 223</userinput>
&gt; <userinput>config set Dhcp4/option-data[0]/csv-format true</userinput>
-&gt; <userinput>config set Dhcp4/option-data[0]/data "192.0.2.100, 123, Hello World"</userinput>
+&gt; <userinput>config set Dhcp4/option-data[0]/data "192.0.2.100, 123, true, Hello World"</userinput>
&gt; <userinput>config commit</userinput></screen>
- </para>
"csv-format" is set "true" to indicate that the "data" field comprises a command-separated
list of values. The values in the "data" must correspond to the types set in
the "record-types" field of the option definition.
+ </para>
+ <note>
+ <para>
+ It is recommended that boolean values are specified using "true" and "false"
+ strings. This helps to prevent errors when typing multiple comma separated
+ values, as it make it easier to identify the type of the value being typed,
+ and compare it with the order of data fields. Nevetheless, it is possible
+ to use integer values: "1" and "0", instead of "true" and "false"
+ accordingly. If other integer value is specified, the configuration is
+ rejected.
+ </para>
+ </note>
</section>
<section id="dhcp4-vendor-opts">
@@ -4471,6 +4495,21 @@ Dhcp4/subnet4 [] list (default)
development and should be treated as <quote>not implemented
yet</quote>, rather than actual limitations.</para>
<itemizedlist>
+ <listitem> <!-- see tickets #3234, #3281 -->
+ <para>
+ On-line configuration has some limitations. Adding new subnets or
+ modifying existing ones work, as is removing the last subnet from
+ the list. However, removing non-last (e.g. removing subnet 1,2 or 3 if
+ there are 4 subnets configured) will cause issues. The problem is
+ caused by simplistic subnet-id assignment. The subnets are always
+ numbered, starting from 1. That subnet-id is then used in leases
+ that are stored in the lease database. Removing non-last subnet will
+ cause the configuration information to mismatch data in the lease
+ database. It is possible to manually update subnet-id fields in
+ MySQL database, but it is awkward and error prone process. A better
+ reconfiguration support is planned.
+ </para>
+ </listitem>
<listitem>
<para>
On startup, the DHCPv4 server does not get the full configuration from
@@ -5052,10 +5091,10 @@ Dhcp6/subnet6/ list
define an option comprising a number of existing primitives.
</para>
<para>
- Assume we
- want to define a new option that will consist of an IPv6
- address, followed by unsigned 16 bit integer, followed by a text
- string. Such an option could be defined in the following way:
+ Assume we want to define a new option that will consist of an IPv6
+ address, followed by an unsigned 16 bit integer, followed by a
+ boolean value, followed by a text string. Such an option could
+ be defined in the following way:
<screen>
&gt; <userinput>config add Dhcp6/option-def</userinput>
&gt; <userinput>config set Dhcp6/option-def[0]/name "bar"</userinput>
@@ -5063,7 +5102,7 @@ Dhcp6/subnet6/ list
&gt; <userinput>config set Dhcp6/option-def[0]/space "dhcp6"</userinput>
&gt; <userinput>config set Dhcp6/option-def[0]/type "record"</userinput>
&gt; <userinput>config set Dhcp6/option-def[0]/array false</userinput>
-&gt; <userinput>config set Dhcp6/option-def[0]/record-types "ipv6-address, uint16, string"</userinput>
+&gt; <userinput>config set Dhcp6/option-def[0]/record-types "ipv6-address, uint16, boolean, string"</userinput>
&gt; <userinput>config set Dhcp6/option-def[0]/encapsulate ""</userinput>
</screen>
The "type" is set to "record" to indicate that the option contains
@@ -5078,12 +5117,23 @@ Dhcp6/subnet6/ list
&gt; <userinput>config set Dhcp6/option-data[0]/space "dhcp6"</userinput>
&gt; <userinput>config set Dhcp6/option-data[0]/code 101</userinput>
&gt; <userinput>config set Dhcp6/option-data[0]/csv-format true</userinput>
-&gt; <userinput>config set Dhcp6/option-data[0]/data "2001:db8:1::10, 123, Hello World"</userinput>
+&gt; <userinput>config set Dhcp6/option-data[0]/data "2001:db8:1::10, 123, false, Hello World"</userinput>
&gt; <userinput>config commit</userinput></screen>
- </para>
"csv-format" is set "true" to indicate that the "data" field comprises a command-separated
list of values. The values in the "data" must correspond to the types set in
the "record-types" field of the option definition.
+ </para>
+ <note>
+ <para>
+ It is recommended that boolean values are specified using "true" and "false"
+ strings. This helps to prevent errors when typing multiple comma separated
+ values, as it make it easier to identify the type of the value being typed,
+ and compare it with the order of data fields. Nevetheless, it is possible
+ to use integer values: "1" and "0", instead of "true" and "false"
+ accordingly. If other integer value is specified, the configuration is
+ rejected.
+ </para>
+ </note>
</section>
<section id="dhcp6-vendor-opts">
@@ -5384,6 +5434,22 @@ should include options from the isc option space:
yet</quote>, rather than actual limitations.</para>
<itemizedlist>
+ <listitem> <!-- see tickets #3234, #3281 -->
+ <para>
+ On-line configuration has some limitations. Adding new subnets or
+ modifying existing ones work, as is removing the last subnet from
+ the list. However, removing non-last (e.g. removing subnet 1,2 or 3 if
+ there are 4 subnets configured) will cause issues. The problem is
+ caused by simplistic subnet-id assignment. The subnets are always
+ numbered, starting from 1. That subnet-id is then used in leases
+ that are stored in the lease database. Removing non-last subnet will
+ cause the configuration information to mismatch data in the lease
+ database. It is possible to manually update subnet-id fields in
+ MySQL database, but it is awkward and error prone process. A better
+ reconfiguration support is planned.
+ </para>
+ </listitem>
+
<listitem>
<para>
On startup, the DHCPv6 server does not get the full configuration from
diff --git a/src/bin/auth/.gitignore b/src/bin/auth/.gitignore
index c3db95bd31..c090cbada8 100644
--- a/src/bin/auth/.gitignore
+++ b/src/bin/auth/.gitignore
@@ -11,3 +11,5 @@
/gen-statisticsitems.py.pre
/statistics.cc
/statistics_items.h
+/s-genstats
+/s-messages
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index e5b655aa9c..5009de8559 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -383,11 +383,11 @@ This message is also logged when the forwarding is restarted (for instance
if b10-ddns is restarted and the internal connection needs to be created
again), in which case it should be followed by AUTH_START_DDNS_FORWARDER.
-% AUTH_UNSUPPORTED_OPCODE unsupported opcode: %1
+% AUTH_UNSUPPORTED_OPCODE unsupported opcode %1 received from %2
This is a debug message, produced when a received DNS packet being
processed by the authoritative server has been found to contain an
-unsupported opcode. (The opcode is included in the message.) The server
-will return an error code of NOTIMPL to the sender.
+unsupported opcode. (The opcode and sender details are included in the
+message.) The server will return an error code of NOTIMPL to the sender.
% AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
This is a debug message indicating that the authoritative server has
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index c0f9730696..63ed888361 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -297,6 +297,8 @@ public:
///
/// \param server The DNSServer as passed to processMessage()
/// \param message The response as constructed by processMessage()
+ /// \param stats_attrs Object to store message attributes in for use
+ /// with statistics
/// \param done If true, it indicates there is a response.
/// this value will be passed to server->resume(bool)
void resumeServer(isc::asiodns::DNSServer* server,
@@ -440,12 +442,9 @@ makeErrorMessage(MessageRenderer& renderer, Message& message,
message.setRcode(rcode);
RendererHolder holder(renderer, &buffer, stats_attrs);
- if (tsig_context.get() != NULL) {
- message.toWire(renderer, *tsig_context);
- stats_attrs.setResponseTSIG(true);
- } else {
- message.toWire(renderer);
- }
+ message.toWire(renderer, tsig_context.get());
+ stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
+
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
.arg(renderer.getLength()).arg(message);
}
@@ -582,8 +581,9 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
Rcode::NOTIMP(), stats_attrs, tsig_context);
}
} else if (opcode != Opcode::QUERY()) {
+ const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
- .arg(message.getOpcode().toText());
+ .arg(message.getOpcode().toText()).arg(remote_ep);
makeErrorMessage(impl_->renderer_, message, buffer,
Rcode::NOTIMP(), stats_attrs, tsig_context);
} else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
@@ -671,12 +671,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
const bool udp_buffer =
(io_message.getSocket().getProtocol() == IPPROTO_UDP);
renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
- if (tsig_context.get() != NULL) {
- message.toWire(renderer_, *tsig_context);
- stats_attrs.setResponseTSIG(true);
- } else {
- message.toWire(renderer_);
- }
+ message.toWire(renderer_, tsig_context.get());
+ stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
+
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
.arg(renderer_.getLength()).arg(message);
return (true);
@@ -833,12 +830,8 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
message.setRcode(Rcode::NOERROR());
RendererHolder holder(renderer_, &buffer, stats_attrs);
- if (tsig_context.get() != NULL) {
- message.toWire(renderer_, *tsig_context);
- stats_attrs.setResponseTSIG(true);
- } else {
- message.toWire(renderer_);
- }
+ message.toWire(renderer_, tsig_context.get());
+ stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
return (true);
}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index b8147e0b14..02d87985cf 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -104,6 +104,8 @@ public:
/// process. It's normally a reference to an xfr::XfroutClient object,
/// but can refer to a local mock object for testing (or other
/// experimental) purposes.
+ /// \param ddns_forwarder Forwarder to which DDNS UPDATE requests
+ /// are passed to
AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
~AuthSrv();
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 87f8e91cdb..9d6fbfa037 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -37,20 +37,6 @@ using namespace isc::dns;
using namespace isc::datasrc;
using namespace isc::dns::rdata;
-// This is a "constant" vector storing desired RR types for the additional
-// section. The vector is filled first time it's used.
-namespace {
-const vector<RRType>&
-A_AND_AAAA() {
- static vector<RRType> needed_types;
- if (needed_types.empty()) {
- needed_types.push_back(RRType::A());
- needed_types.push_back(RRType::AAAA());
- }
- return (needed_types);
-}
-}
-
namespace isc {
namespace auth {
@@ -393,6 +379,17 @@ Query::process(datasrc::ClientList& client_list,
response_->setRcode(Rcode::SERVFAIL());
return;
}
+
+ if (qtype == RRType::RRSIG()) {
+ // We will not serve RRSIGs directly. See #2226 and the
+ // following thread for discussion why:
+ // http://www.ietf.org/mail-archive/web/dnsext/current/msg07123.html
+ // RRSIGs go together with their covered RRset.
+ response_->setHeaderFlag(Message::HEADERFLAG_AA);
+ response_->setRcode(Rcode::REFUSED());
+ return;
+ }
+
ZoneFinder& zfinder = *result.finder_;
// We have authority for a zone that contain the query name (possibly
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 2c08312b14..a692dcc694 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -286,6 +286,9 @@ public:
answers_.reserve(RESERVE_RRSETS);
authorities_.reserve(RESERVE_RRSETS);
additionals_.reserve(RESERVE_RRSETS);
+
+ a_and_aaaa_.push_back(isc::dns::RRType::A());
+ a_and_aaaa_.push_back(isc::dns::RRType::AAAA());
}
@@ -488,6 +491,15 @@ private:
std::vector<isc::dns::ConstRRsetPtr> answers_;
std::vector<isc::dns::ConstRRsetPtr> authorities_;
std::vector<isc::dns::ConstRRsetPtr> additionals_;
+
+private:
+ /// \brief Returns a reference to a pre-initialized vector (see the
+ /// \c Query constructor).
+ const std::vector<isc::dns::RRType>& A_AND_AAAA() const {
+ return (a_and_aaaa_);
+ }
+
+ std::vector<isc::dns::RRType> a_and_aaaa_;
};
}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index f374a87384..193fb914ef 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -1215,6 +1215,13 @@ TEST_P(QueryTest, exactMatchMultipleQueries) {
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
+TEST_P(QueryTest, qtypeIsRRSIG) {
+ // Directly querying for RRSIGs should result in rcode=REFUSED.
+ EXPECT_NO_THROW(query.process(*list_, qname, RRType::RRSIG(), response));
+ responseCheck(response, Rcode::REFUSED(), AA_FLAG, 0, 0, 0,
+ "", "", "");
+}
+
TEST_P(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index bcae95c49c..a83775bb19 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -446,8 +446,9 @@ WARNING: The Python readline module isn't available, so some command line
raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
param_nr += 1
- # Convert parameter value according parameter spec file.
- # Ignore check for commands belongs to module 'config' or 'execute
+ # Convert parameter value according to parameter spec
+ # file. Ignore check for commands belonging to module 'config'
+ # or 'execute'.
if cmd.module != CONFIG_MODULE_NAME and\
cmd.module != command_sets.EXECUTE_MODULE_NAME:
for param_name in cmd.params:
diff --git a/src/bin/d2/.gitignore b/src/bin/d2/.gitignore
index d1478024d8..9726436a1a 100644
--- a/src/bin/d2/.gitignore
+++ b/src/bin/d2/.gitignore
@@ -4,3 +4,4 @@
/d2_messages.h
/spec_config.h
/spec_config.h.pre
+/s-messages
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
index ef63a0ad94..efa4de5560 100644
--- a/src/bin/d2/d2_cfg_mgr.cc
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -119,7 +119,7 @@ std::string
D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
if (!ioaddr.isV4()) {
isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
- << ioaddr.toText());
+ << ioaddr);
}
// Get the address in byte vector form.
@@ -148,8 +148,7 @@ D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
std::string
D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
if (!ioaddr.isV6()) {
- isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: "
- << ioaddr.toText());
+ isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
}
// Turn the address into a string of digits.
diff --git a/src/bin/d2/dns_client.h b/src/bin/d2/dns_client.h
index 5960f6a147..54d6a6a637 100644
--- a/src/bin/d2/dns_client.h
+++ b/src/bin/d2/dns_client.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -143,8 +143,8 @@ public:
/// @param ns_addr DNS server address.
/// @param ns_port DNS server port.
/// @param update A DNS Update message to be sent to the server.
- /// @param wait A timeout (in seconds) for the response. If a response is
- /// not received within the timeout, exchange is interrupted. This value
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
/// must not exceed maximal value for 'int' data type.
/// @param tsig_key An @c isc::dns::TSIGKey object representing TSIG
/// context which will be used to render the DNS Update message.
@@ -173,8 +173,8 @@ public:
/// @param ns_addr DNS server address.
/// @param ns_port DNS server port.
/// @param update A DNS Update message to be sent to the server.
- /// @param wait A timeout (in seconds) for the response. If a response is
- /// not received within the timeout, exchange is interrupted. This value
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
/// must not exceed maximal value for 'int' data type.
void doUpdate(asiolink::IOService& io_service,
const asiolink::IOAddress& ns_addr,
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
index c97b744ce1..2711a71f9d 100644
--- a/src/bin/d2/tests/d2_update_mgr_unittests.cc
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -213,7 +213,7 @@ public:
/// vary.
void processAll(unsigned int timeout_millisec =
NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT + 100,
- size_t max_passes = 20) {
+ size_t max_passes = 100) {
// Loop until all the transactions have been dequeued and run through to
// completion.
size_t passes = 0;
@@ -238,9 +238,9 @@ public:
}
// This is a last resort fail safe to ensure we don't go around
- // forever. We cut it off the number of passes at 20. This is
- // roughly twice the number for the longest test (currently,
- // multiTransactionTimeout).
+ // forever. We cut it off the number of passes at 100 (default
+ // value). This is roughly ten times the number for the longest
+ // test (currently, multiTransactionTimeout).
if (passes > max_passes) {
ADD_FAILURE() << "processALL failed, too many passes: "
<< passes << ", total handlers executed: " << handlers;
diff --git a/src/bin/d2/tests/dns_client_unittests.cc b/src/bin/d2/tests/dns_client_unittests.cc
index a24196ea97..0bfc4b4343 100644
--- a/src/bin/d2/tests/dns_client_unittests.cc
+++ b/src/bin/d2/tests/dns_client_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -128,7 +128,7 @@ public:
response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
D2ZonePtr zone = response_->getZone();
ASSERT_TRUE(zone);
- EXPECT_EQ("response.example.com.", zone->getName().toText());
+ EXPECT_EQ("example.com.", zone->getName().toText());
EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
} else {
@@ -263,8 +263,16 @@ public:
ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
- // Set the response wait time to 0 so as our test is not hanging. This
- // should cause instant timeout.
+ /// @todo The timeout value could be set to 0 to trigger timeout
+ /// instantly. However, it may lead to situations that the message sent
+ /// in one test will not be dropped by the kernel by the time, the next
+ /// test starts. This will lead to intermittent unit test errors as
+ /// described in the ticket http://bind10.isc.org/ticket/3265.
+ /// Increasing the timeout to a non-zero value mitigates this problem.
+ /// The proper way to solve this problem is to receive the packet
+ /// on our own and drop it. Such a fix will need to be applied not only
+ /// to this test but also for other tests that rely on arbitrary timeout
+ /// values.
const int timeout = 500;
// The doUpdate() function starts asynchronous message exchange with DNS
// server. When message exchange is done or timeout occurs, the
@@ -288,7 +296,7 @@ public:
// Create a request DNS Update message.
D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
- ASSERT_NO_THROW(message.setZone(Name("response.example.com"), RRClass::IN()));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
// In order to perform the full test, when the client sends the request
// and receives a response from the server, we have to emulate the
@@ -324,8 +332,10 @@ public:
corrupt_response));
// The socket is now ready to receive the data. Let's post some request
- // message then.
- const int timeout = 5;
+ // message then. Set timeout to some reasonable value to make sure that
+ // there is sufficient amount of time for the test to generate a
+ // response.
+ const int timeout = 500;
expected_++;
dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
message, timeout);
diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc
index 9aecda40fe..05e8a1a559 100644
--- a/src/bin/d2/tests/nc_add_unittests.cc
+++ b/src/bin/d2/tests/nc_add_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -187,19 +187,12 @@ typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
///
/// Note this class uses NameAddStub class to exercise non-public
/// aspects of NameAddTransaction.
-class NameAddTransactionTest : public ::testing::Test {
+class NameAddTransactionTest : public TransactionTest {
public:
- IOServicePtr io_service_;
- DdnsDomainPtr forward_domain_;
- DdnsDomainPtr reverse_domain_;
- NameAddTransactionTest() : io_service_(new isc::asiolink::IOService()) {
+ NameAddTransactionTest() {
}
- static const unsigned int FORWARD_CHG = 0x01;
- static const unsigned int REVERSE_CHG = 0x02;
- static const unsigned int FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
-
virtual ~NameAddTransactionTest() {
}
@@ -211,53 +204,12 @@ public:
/// will have either the forward, reverse, or both domains populated.
///
/// @param change_mask determines which change directions are requested
- NameAddStubPtr makeTransaction4(int change_mask=FWD_AND_REV_CHG) {
- const char* msg_str =
- "{"
- " \"change_type\" : 0 , "
- " \"forward_change\" : true , "
- " \"reverse_change\" : true , "
- " \"fqdn\" : \"my.forward.example.com.\" , "
- " \"ip_address\" : \"192.168.2.1\" , "
- " \"dhcid\" : \"0102030405060708\" , "
- " \"lease_expires_on\" : \"20130121132405\" , "
- " \"lease_length\" : 1300 "
- "}";
-
- // Create NameChangeRequest from JSON string.
- dhcp_ddns::NameChangeRequestPtr ncr = dhcp_ddns::NameChangeRequest::
- fromJSON(msg_str);
-
- // If the change mask does not include a forward change clear the
- // forward domain; otherwise create the domain and its servers.
- if (!(change_mask & FORWARD_CHG)) {
- ncr->setForwardChange(false);
- forward_domain_.reset();
- } else {
- // Create the forward domain and then its servers.
- forward_domain_ = makeDomain("example.com.");
- addDomainServer(forward_domain_, "forward.example.com",
- "1.1.1.1");
- addDomainServer(forward_domain_, "forward2.example.com",
- "1.1.1.2");
- }
-
- // If the change mask does not include a reverse change clear the
- // reverse domain; otherwise create the domain and its servers.
- if (!(change_mask & REVERSE_CHG)) {
- ncr->setReverseChange(false);
- reverse_domain_.reset();
- } else {
- // Create the reverse domain and its server.
- reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.");
- addDomainServer(reverse_domain_, "reverse.example.com",
- "2.2.2.2");
- addDomainServer(reverse_domain_, "reverse2.example.com",
- "2.2.2.3");
- }
+ NameAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
// Now create the test transaction as would occur in update manager.
- return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
forward_domain_,
reverse_domain_)));
}
@@ -270,53 +222,16 @@ public:
/// will have either the forward, reverse, or both domains populated.
///
/// @param change_mask determines which change directions are requested
- NameAddStubPtr makeTransaction6(int change_mask=FWD_AND_REV_CHG) {
- const char* msg_str =
- "{"
- " \"change_type\" : 0 , "
- " \"forward_change\" : true , "
- " \"reverse_change\" : true , "
- " \"fqdn\" : \"my6.forward.example.com.\" , "
- " \"ip_address\" : \"2001:1::100\" , "
- " \"dhcid\" : \"0102030405060708\" , "
- " \"lease_expires_on\" : \"20130121132405\" , "
- " \"lease_length\" : 1300 "
- "}";
-
- // Create NameChangeRequest from JSON string.
- dhcp_ddns::NameChangeRequestPtr ncr = makeNcrFromString(msg_str);
-
- // If the change mask does not include a forward change clear the
- // forward domain; otherwise create the domain and its servers.
- if (!(change_mask & FORWARD_CHG)) {
- ncr->setForwardChange(false);
- forward_domain_.reset();
- } else {
- // Create the forward domain and then its servers.
- forward_domain_ = makeDomain("example.com.");
- addDomainServer(forward_domain_, "fwd6-server.example.com",
- "2001:1::5");
- }
-
- // If the change mask does not include a reverse change clear the
- // reverse domain; otherwise create the domain and its servers.
- if (!(change_mask & REVERSE_CHG)) {
- ncr->setReverseChange(false);
- reverse_domain_.reset();
- } else {
- // Create the reverse domain and its server.
- reverse_domain_ = makeDomain("1.2001.ip6.arpa.");
- addDomainServer(reverse_domain_, "rev6-server.example.com",
- "2001:1::6");
- }
+ NameAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
// Now create the test transaction as would occur in update manager.
- return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
forward_domain_,
reverse_domain_)));
}
-
/// @brief Create a test transaction at a known point in the state model.
///
/// Method prepares a new test transaction and sets its state and next
@@ -329,15 +244,19 @@ public:
/// @param state value to set as the current state
/// @param event value to post as the next event
/// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
NameAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
- unsigned int change_mask = FWD_AND_REV_CHG) {
- NameAddStubPtr name_add = makeTransaction4(change_mask);
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ NameAddStubPtr name_add = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction4(change_mask));
name_add->initDictionaries();
name_add->postNextEvent(event);
name_add->setState(state);
return (name_add);
}
-
};
/// @brief Tests NameAddTransaction construction.
diff --git a/src/bin/d2/tests/nc_test_utils.cc b/src/bin/d2/tests/nc_test_utils.cc
index 5ec178d298..a907abeb23 100644
--- a/src/bin/d2/tests/nc_test_utils.cc
+++ b/src/bin/d2/tests/nc_test_utils.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,8 @@
#include <dns/opcode.h>
#include <dns/messagerenderer.h>
#include <nc_test_utils.h>
+#include <asio.hpp>
+#include <asiolink/udp_endpoint.h>
#include <gtest/gtest.h>
@@ -42,7 +44,9 @@ FauxServer::FauxServer(asiolink::IOService& io_service,
server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
asio::ip::udp::v4()));
server_socket_->set_option(asio::socket_base::reuse_address(true));
- server_socket_->bind(asio::ip::udp::endpoint(address_.getAddress(), port_));
+
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
}
FauxServer::FauxServer(asiolink::IOService& io_service,
@@ -53,7 +57,8 @@ FauxServer::FauxServer(asiolink::IOService& io_service,
server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
asio::ip::udp::v4()));
server_socket_->set_option(asio::socket_base::reuse_address(true));
- server_socket_->bind(asio::ip::udp::endpoint(address_.getAddress(), port_));
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
}
@@ -172,7 +177,7 @@ TimedIO::runTimedIO(int run_time) {
run_time_ = run_time;
int cnt = io_service_->get_io_service().poll();
if (cnt == 0) {
- timer_.setup(boost::bind(&TransactionTest::timesUp, this), run_time_);
+ timer_.setup(boost::bind(&TimedIO::timesUp, this), run_time_);
cnt = io_service_->get_io_service().run_one();
timer_.cancel();
}
diff --git a/src/bin/d2/tests/nc_trans_unittests.cc b/src/bin/d2/tests/nc_trans_unittests.cc
index 0829ff844e..a62d0ae474 100644
--- a/src/bin/d2/tests/nc_trans_unittests.cc
+++ b/src/bin/d2/tests/nc_trans_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -275,83 +275,65 @@ typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
///
/// Note this class uses NameChangeStub class to exercise non-public
/// aspects of NameChangeTransaction.
-class NameChangeTransactionTest : public ::testing::Test {
+class NameChangeTransactionTest : public TransactionTest {
public:
- IOServicePtr io_service_;
- DdnsDomainPtr forward_domain_;
- DdnsDomainPtr reverse_domain_;
- asiolink::IntervalTimer timer_;
- int run_time_;
-
- NameChangeTransactionTest()
- : io_service_(new isc::asiolink::IOService()), timer_(*io_service_),
- run_time_(0) {
+ NameChangeTransactionTest() {
}
virtual ~NameChangeTransactionTest() {
}
- /// @brief Run the IO service for no more than a given amount of time.
- ///
- /// Uses an IntervalTimer to interrupt the invocation of IOService run(),
- /// after the given number of milliseconds elapse. The timer executes
- /// the timesUp() method if it expires.
- ///
- /// @param run_time amount of time in milliseconds to allow run to execute.
- void runTimedIO(int run_time) {
- run_time_ = run_time;
- timer_.setup(boost::bind(&NameChangeTransactionTest::timesUp, this),
- run_time_);
- io_service_->run();
- }
-
- /// @brief IO Timer expiration handler
- ///
- /// Stops the IOSerivce and fails the current test.
- void timesUp() {
- io_service_->stop();
- FAIL() << "Test Time: " << run_time_ << " expired";
- }
-
/// @brief Instantiates a NameChangeStub test transaction
/// The transaction is constructed around a predefined (i.e "canned")
/// NameChangeRequest. The request has both forward and reverse DNS
/// changes requested, and both forward and reverse domains are populated.
NameChangeStubPtr makeCannedTransaction() {
- // NCR in JSON form.
- const char* msg_str =
- "{"
- " \"change_type\" : 0 , "
- " \"forward_change\" : true , "
- " \"reverse_change\" : true , "
- " \"fqdn\" : \"my.example.com.\" , "
- " \"ip_address\" : \"192.168.2.1\" , "
- " \"dhcid\" : \"0102030405060708\" , "
- " \"lease_expires_on\" : \"20130121132405\" , "
- " \"lease_length\" : 1300 "
- "}";
-
- // Create the request from JSON.
- dhcp_ddns::NameChangeRequestPtr ncr = dhcp_ddns::NameChangeRequest::
- fromJSON(msg_str);
-
- // Make forward DdnsDomain with 2 forward servers.
- forward_domain_ = makeDomain("example.com.");
- addDomainServer(forward_domain_, "forward.example.com",
- "127.0.0.1", 5301);
- addDomainServer(forward_domain_, "forward2.example.com",
- "127.0.0.1", 5302);
-
- // Make reverse DdnsDomain with one reverse server.
- reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.");
- addDomainServer(reverse_domain_, "reverse.example.com",
- "127.0.0.1", 5301);
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG);
+ // Now create the test transaction as would occur in update manager.
// Instantiate the transaction as would be done by update manager.
- return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr,
+ return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
forward_domain_, reverse_domain_)));
}
+ /// @brief Builds and then sends an update request
+ ///
+ /// This method is used to build and send and update request. It is used
+ /// in conjuction with FauxServer to test various message response
+ /// scenarios.
+ /// @param name_change Transaction under test
+ /// @param run_time Maximum time to permit IO processing to run before
+ /// timing out (in milliseconds)
+ void doOneExchange(NameChangeStubPtr name_change,
+ unsigned int run_time = 500) {
+ // Create a valid request for the transaction.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::
+ OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+ req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+ req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+ // Set the flag to use the NameChangeStub's DNSClient callback.
+ name_change->use_stub_callback_ = true;
+
+ // Invoke sendUpdate.
+ ASSERT_NO_THROW(name_change->sendUpdate());
+
+ // Update attempt count should be 1, next event should be NOP_EVT.
+ ASSERT_EQ(1, name_change->getUpdateAttempts());
+ ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+ name_change->getNextEvent());
+
+ while (name_change->getNextEvent() == NameChangeTransaction::NOP_EVT) {
+ int cnt = 0;
+ ASSERT_NO_THROW(cnt = runTimedIO(run_time));
+ if (cnt == 0) {
+ FAIL() << "IO Service stopped unexpectedly";
+ }
+ }
+ }
};
/// @brief Tests NameChangeTransaction construction.
@@ -874,27 +856,16 @@ TEST_F(NameChangeTransactionTest, sendUpdateTimeout) {
ASSERT_NO_THROW(name_change->initDictionaries());
ASSERT_TRUE(name_change->selectFwdServer());
- // Create a valid request.
- D2UpdateMessagePtr req;
- ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
- ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
- req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
- req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
- // Set the flag to use the NameChangeStub's DNSClient callback.
- name_change->use_stub_callback_ = true;
-
- // Invoke sendUpdate.
- ASSERT_NO_THROW(name_change->sendUpdate());
-
- // Update attempt count should be 1, next event should be NOP_EVT.
- EXPECT_EQ(1, name_change->getUpdateAttempts());
- ASSERT_EQ(NameChangeTransaction::NOP_EVT,
- name_change->getNextEvent());
-
- // Run IO a bit longer than maximum allowed to permit timeout logic to
- // execute.
- runTimedIO(NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT + 100);
+ // Build a valid request, call sendUpdate and process the response.
+ // Note we have to wait for DNSClient timeout plus a bit more to allow
+ // DNSClient to timeout.
+ // The method, doOneExchange, can suffer fatal assertions which invalidate
+ // not only it but the invoking test as well. In other words, if the
+ // doOneExchange blows up the rest of test is pointless. I use
+ // ASSERT_NO_FATAL_FAILURE to abort the test immediately.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change,
+ NameChangeTransaction::
+ DNS_UPDATE_DEFAULT_TIMEOUT + 100));
// Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT.
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
@@ -914,26 +885,8 @@ TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) {
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
server.receive(FauxServer::CORRUPT_RESP);
- // Create a valid request for the transaction.
- D2UpdateMessagePtr req;
- ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
- ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
- req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
- req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
- // Set the flag to use the NameChangeStub's DNSClient callback.
- name_change->use_stub_callback_ = true;
-
- // Invoke sendUpdate.
- ASSERT_NO_THROW(name_change->sendUpdate());
-
- // Update attempt count should be 1, next event should be NOP_EVT.
- EXPECT_EQ(1, name_change->getUpdateAttempts());
- ASSERT_EQ(NameChangeTransaction::NOP_EVT,
- name_change->getNextEvent());
-
- // Run the IO for 500 ms. This should be more than enough time.
- runTimedIO(500);
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
// Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID.
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
@@ -952,26 +905,8 @@ TEST_F(NameChangeTransactionTest, sendUpdate) {
FauxServer server(*io_service_, *(name_change->getCurrentServer()));
server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
- // Create a valid request for the transaction.
- D2UpdateMessagePtr req;
- ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
- ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
- req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
- req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
- // Set the flag to use the NameChangeStub's DNSClient callback.
- name_change->use_stub_callback_ = true;
-
- // Invoke sendUpdate.
- ASSERT_NO_THROW(name_change->sendUpdate());
-
- // Update attempt count should be 1, next event should be NOP_EVT.
- EXPECT_EQ(1, name_change->getUpdateAttempts());
- ASSERT_EQ(NameChangeTransaction::NOP_EVT,
- name_change->getNextEvent());
-
- // Run the IO for 500 ms. This should be more than enough time.
- runTimedIO(500);
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
// Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
index 6064d3d418..b351f88d1e 100644
--- a/src/bin/d2/tests/test_data_files_config.h.in
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index 6bb81c5b6c..f87a21f352 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -495,10 +495,7 @@ class DDNSServer:
return False
msg = update_session.get_message()
self.__response_renderer.clear()
- if tsig_ctx is not None:
- msg.to_wire(self.__response_renderer, tsig_ctx)
- else:
- msg.to_wire(self.__response_renderer)
+ msg.to_wire(self.__response_renderer, tsig_ctx)
ret = self.__send_response(sock, self.__response_renderer.get_data(),
remote_addr)
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 66e87a4e4c..c5db61aa71 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -900,10 +900,7 @@ def create_msg(opcode=Opcode.UPDATE, zones=[TEST_ZONE_RECORD], prereq=[],
msg.add_rrset(SECTION_PREREQUISITE, p)
renderer = MessageRenderer()
- if tsigctx is not None:
- msg.to_wire(renderer, tsigctx)
- else:
- msg.to_wire(renderer)
+ msg.to_wire(renderer, tsigctx)
# re-read the created data in the parse mode
msg.clear(Message.PARSE)
diff --git a/src/bin/dhcp4/.gitignore b/src/bin/dhcp4/.gitignore
index 86965b9f56..2ca0e8093f 100644
--- a/src/bin/dhcp4/.gitignore
+++ b/src/bin/dhcp4/.gitignore
@@ -4,3 +4,4 @@
/dhcp4_messages.h
/spec_config.h
/spec_config.h.pre
+/s-messages
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 777cce5d2b..7774edcaa0 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -265,7 +265,7 @@ protected:
Triplet<uint32_t> valid = getParam("valid-lifetime");
stringstream tmp;
- tmp << addr.toText() << "/" << (int)len
+ tmp << addr << "/" << (int)len
<< " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
@@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
parser = new HooksLibrariesParser(config_id);
} else if (config_id.compare("echo-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
+ } else if (config_id.compare("dhcp-ddns") == 0) {
+ parser = new D2ClientConfigParser(config_id);
} else {
isc_throw(NotImplemented,
"Parser error: Global configuration parameter not supported: "
@@ -433,6 +435,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
DHCP4_CONFIG_START).arg(config_set->str());
+ // Before starting any subnet operations, let's reset the subnet-id counter,
+ // so newly recreated configuration starts with first subnet-id equal 1.
+ Subnet::resetSubnetID();
+
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet4 structure
// depend on the global values. Also, option values configuration
@@ -448,7 +454,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Some of the parsers alter the state of the system in a way that can't
// easily be undone. (Or alter it in a way such that undoing the change has
// the same risk of failure as doing the change.)
- ParserPtr hooks_parser_;
+ ParserPtr hooks_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
@@ -489,7 +495,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Executing commit will alter currently-loaded hooks
// libraries. Check if the supplied libraries are valid,
// but defer the commit until everything else has committed.
- hooks_parser_ = parser;
+ hooks_parser = parser;
parser->build(config_pair.second);
} else {
// Those parsers should be started before other
@@ -557,8 +563,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// This occurs last as if it succeeds, there is no easy way
// revert it. As a result, the failure to commit a subsequent
// change causes problems when trying to roll back.
- if (hooks_parser_) {
- hooks_parser_->commit();
+ if (hooks_parser) {
+ hooks_parser->commit();
}
}
catch (const isc::Exception& ex) {
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 43c08c9d74..72e0b219d9 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -226,7 +226,7 @@ void ControlledDhcpv4Srv::establishSession() {
int ctrl_socket = cc_session_->getSocketDesc();
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
.arg(ctrl_socket);
- IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
+ IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
}
void ControlledDhcpv4Srv::disconnectSession() {
@@ -235,13 +235,14 @@ void ControlledDhcpv4Srv::disconnectSession() {
config_session_ = NULL;
}
if (cc_session_) {
+
+ int ctrl_socket = cc_session_->getSocketDesc();
cc_session_->disconnect();
+
+ IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
delete cc_session_;
cc_session_ = NULL;
}
-
- // deregister session socket
- IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
}
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 526d98753c..59dde870ee 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -107,7 +107,8 @@ protected:
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
- /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ /// be later accessed with
+ /// \ref isc::config::ConfigData::getFullConfig().
///
/// @param new_config new configuration.
///
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
index 0d457c6b48..aa43ee3267 100644
--- a/src/bin/dhcp4/dhcp4.dox
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -155,6 +155,36 @@ The default behaviour is constituted by the set of constants defined in the
(upper part of) dhcp4_srv.cc file. Once the configuration is implemented,
these constants will be removed.
+@section dhcpv4Classifier DHCPv4 Client Classification
+
+Kea DHCPv4 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (60) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv4Srv::classifyPacket() method is called.
+It attempts to extract content of the vendor class option and interpret as a name
+of the class. For now, the code has been tested with two classes used in cable modem
+networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will
+be interpreted as a class name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should
+be used.
+
+Currently there is a short code section that alternates packet processing depending on
+which class it belongs to. It is planned to move that capability to an external hook
+library. See ticket #3275. The class specific behavior is:
+
+- docsis3.0 packets have siaddr (next server) field set
+- docsis3.0 packets have file field set to the content of the boot-file-name option
+- eRouter1.0 packets have siaddr (next server) field cleared
+
+Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
+
@section dhcpv4Other Other DHCPv4 topics
For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 9372dfd8ad..04bcc7ac61 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -292,7 +292,96 @@
}
} ]
}
- }
+ },
+
+ { "item_name": "dhcp-ddns",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"enable-updates": false},
+ "item_description" : "Contains parameters pertaining DHCP-driven DDNS updates",
+ "map_item_spec": [
+ {
+ "item_name": "enable-updates",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false,
+ "item_description" : "Enables DDNS update processing"
+ },
+ {
+ "item_name": "server-ip",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "127.0.0.1",
+ "item_description" : "IP address of b10-dhcp-ddns (IPv4 or IPv6)"
+ },
+ {
+ "item_name": "server-port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53001,
+ "item_description" : "port number of b10-dhcp-ddns"
+ },
+ {
+ "item_name": "ncr-protocol",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "UDP",
+ "item_description" : "Socket protocol to use with b10-dhcp-ddns"
+ },
+ {
+ "item_name": "ncr-format",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "JSON",
+ "item_description" : "Format of the update request packet"
+ },
+ {
+
+ "item_name": "always-include-fqdn",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false,
+ "item_description": "Enable always including the FQDN option in its response"
+ },
+ {
+ "item_name": "override-no-update",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false,
+ "item_description": "Do update, even if client requested no updates with N flag"
+ },
+ {
+ "item_name": "override-client-update",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false,
+ "item_description": "Server performs an update even if client requested delegation"
+ },
+ {
+ "item_name": "replace-client-name",
+ "item_type": "boolean",
+ "item_optional": true,
+ "item_default": false,
+ "item_description": "Should server replace the domain-name supplied by the client"
+ },
+ {
+ "item_name": "generated-prefix",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "myhost",
+ "item_description": "Prefix to use when generating the client's name"
+ },
+
+ {
+ "item_name": "qualifying-suffix",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "example.com",
+ "item_description": "Fully qualified domain-name suffix if partial name provided by client"
+ },
+ ]
+ },
+
],
"commands": [
{
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index f561695f90..297acfdc75 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -32,6 +32,14 @@ This debug message is issued when the DHCP server was unable to process the
FQDN or Hostname option sent by a client. This is likely because the client's
name was malformed or due to internal server error.
+% DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
+% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
+
% DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv4 DHCP server.
@@ -175,6 +183,13 @@ IPv4 DHCP server but it is not running.
A debug message issued during startup, this indicates that the IPv4 DHCP
server is about to open sockets on the specified port.
+% DHCP4_PACKET_NOT_FOR_US received DHCPv4 message (transid=%1, iface=%2) dropped because it contains foreign server identifier
+This debug message is issued when received DHCPv4 message is dropped because
+it is addressed to a different server, i.e. a server identifier held by
+this message doesn't match the identifier used by our server. The arguments
+of this message hold the name of the transaction id and interface on which
+the message has been received.
+
% DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
A warning message issued when IfaceMgr fails to open and bind a socket. The reason
for the failure is appended as an argument of the log message.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 39dde03388..46ad0bbe2b 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_vendor.h>
+#include <dhcp/option_string.h>
#include <dhcp/pkt4.h>
#include <dhcp/docsis3_option_defs.h>
#include <dhcp4/dhcp4_log.h>
@@ -79,36 +80,6 @@ Dhcp4Hooks Hooks;
namespace isc {
namespace dhcp {
-namespace {
-
-// @todo The following constants describe server's behavior with respect to the
-// DHCPv4 Client FQDN Option sent by a client. They will be removed
-// when DDNS parameters for DHCPv4 are implemented with the ticket #3033.
-
-// @todo Additional configuration parameter which we may consider is the one
-// that controls whether the DHCP server sends the removal NameChangeRequest
-// if it discovers that the entry for the particular client exists or that
-// it always updates the DNS.
-
-// Should server always include the FQDN option in its response, regardless
-// if it has been requested in Parameter Request List Option (Disabled).
-const bool FQDN_ALWAYS_INCLUDE = false;
-// Enable A RR update delegation to the client (Disabled).
-const bool FQDN_ALLOW_CLIENT_UPDATE = false;
-// Globally enable updates (Enabled).
-const bool FQDN_ENABLE_UPDATE = true;
-// Do update, even if client requested no updates with N flag (Disabled).
-const bool FQDN_OVERRIDE_NO_UPDATE = false;
-// Server performs an update when client requested delegation (Enabled).
-const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
-// The fully qualified domain-name suffix if partial name provided by
-// a client.
-const char* FQDN_PARTIAL_SUFFIX = "example.com";
-// Should server replace the domain-name supplied by the client (Disabled).
-const bool FQDN_REPLACE_CLIENT_NAME = false;
-
-}
-
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
const bool direct_response_desired)
: shutdown_(true), alloc_engine_(), port_(port),
@@ -259,6 +230,15 @@ Dhcpv4Srv::run() {
}
}
+ // Check if the DHCPv4 packet has been sent to us or to someone else.
+ // If it hasn't been sent to us, drop it!
+ if (!acceptServerId(query)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
+ .arg(query->getTransid())
+ .arg(query->getIface());
+ continue;
+ }
+
// When receiving a packet without message type option, getType() will
// throw. Let's set type to -1 as default error indicator.
int type = -1;
@@ -303,6 +283,9 @@ Dhcpv4Srv::run() {
callout_handle->getArgument("query4", query);
}
+ // Assign this packet to one or more classes if needed
+ classifyPacket(query);
+
try {
switch (query->getType()) {
case DHCPDISCOVER:
@@ -359,6 +342,18 @@ Dhcpv4Srv::run() {
continue;
}
+ // Let's do class specific processing. This is done before
+ // pkt4_send.
+ //
+ /// @todo: decide whether we want to add a new hook point for
+ /// doing class specific processing.
+ if (!classSpecificProcessing(query, rsp)) {
+ /// @todo add more verbosity here
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_PROCESSING_FAILED);
+
+ continue;
+ }
+
// Specifies if server should do the packing
bool skip_pack = false;
@@ -571,8 +566,8 @@ Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) {
// The source address for the outbound message should have been set already.
// This is the address that to the best of the server's knowledge will be
// available from the client.
- // @todo: perhaps we should consider some more sophisticated server id
- // generation, but for the current use cases, it should be ok.
+ /// @todo: perhaps we should consider some more sophisticated server id
+ /// generation, but for the current use cases, it should be ok.
response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
response->getLocalAddr()))
);
@@ -641,8 +636,8 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
uint32_t vendor_id = vendor_req->getVendorId();
// Let's try to get ORO within that vendor-option
- /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
- /// may have different policies.
+ /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
+ /// vendors may have different policies.
OptionUint8ArrayPtr oro =
boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
@@ -748,68 +743,19 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
// response to a client.
Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
- // RFC4702, section 4 - set 'NOS' flags to 0.
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, 0);
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, 0);
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, 0);
-
- // Conditions when N flag has to be set to indicate that server will not
- // perform DNS updates:
- // 1. Updates are globally disabled,
- // 2. Client requested no update and server respects it,
- // 3. Client requested that the forward DNS update is delegated to the
- // client but server neither respects requests for forward update
- // delegation nor it is configured to send update on its own when
- // client requested delegation.
- if (!FQDN_ENABLE_UPDATE ||
- (fqdn->getFlag(Option4ClientFqdn::FLAG_N) &&
- !FQDN_OVERRIDE_NO_UPDATE) ||
- (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
- !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, true);
-
- // Conditions when S flag is set to indicate that server will perform DNS
- // update on its own:
- // 1. Client requested that server performs DNS update and DNS updates are
- // globally enabled.
- // 2. Client requested that server delegates forward update to the client
- // but server doesn't respect requests for delegation and it is
- // configured to perform an update on its own when client requested the
- // delegation.
- } else if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) ||
- (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
- !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, true);
- }
+ // Set the server S, N, and O flags based on client's flags and
+ // current configuration.
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp);
- // Server MUST set the O flag if it has overriden the client's setting
- // of S flag.
- if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) !=
- fqdn_resp->getFlag(Option4ClientFqdn::FLAG_S)) {
- fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, true);
- }
+ // Carry over the client's E flag.
+ fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
+ fqdn->getFlag(Option4ClientFqdn::FLAG_E));
- // If client suppled partial or empty domain-name, server should generate
- // one.
- if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) {
- std::ostringstream name;
- if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
- fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
- } else {
- name << fqdn->getDomainName();
- name << "." << FQDN_PARTIAL_SUFFIX;
- fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
-
- }
-
- // Server may be configured to replace a name supplied by a client, even if
- // client supplied fully qualified domain-name. The empty domain-name is
- // is set to indicate that the name must be generated when the new lease
- // is acquired.
- } else if(FQDN_REPLACE_CLIENT_NAME) {
- fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
- }
+ // Adjust the domain name based on domain name value and type sent by the
+ // client and current configuration.
+ d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
// Add FQDN option to the response message. Note that, there may be some
// cases when server may choose not to include the FQDN option in a
@@ -829,15 +775,20 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
void
Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
Pkt4Ptr& answer) {
+ // Fetch D2 configuration.
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
// Do nothing if the DNS updates are disabled.
- if (!FQDN_ENABLE_UPDATE) {
+ if (!d2_mgr.ddnsEnabled()) {
return;
}
std::string hostname = isc::util::str::trim(opt_hostname->readString());
unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
// The hostname option sent by the client should be at least 1 octet long.
- // If it isn't we ignore this option.
+ // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
+ /// @todo It would be more liberal to accept this and let it fall into
+ /// the case of replace or less than two below.
if (label_count == 0) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_EMPTY_HOSTNAME);
return;
@@ -855,21 +806,20 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
// By checking the number of labels present in the hostname we may infer
// whether client has sent the fully qualified or unqualified hostname.
- // @todo We may want to reconsider whether it is appropriate for the
- // client to send a root domain name as a Hostname. There are
- // also extensions to the auto generation of the client's name,
- // e.g. conversion to the puny code which may be considered at some point.
- // For now, we just remain liberal and expect that the DNS will handle
- // conversion if needed and possible.
- if (FQDN_REPLACE_CLIENT_NAME || (label_count < 2)) {
+ /// @todo We may want to reconsider whether it is appropriate for the
+ /// client to send a root domain name as a Hostname. There are
+ /// also extensions to the auto generation of the client's name,
+ /// e.g. conversion to the puny code which may be considered at some point.
+ /// For now, we just remain liberal and expect that the DNS will handle
+ /// conversion if needed and possible.
+ if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
+ (label_count < 2)) {
opt_hostname_resp->writeString("");
- // If there are two labels, it means that the client has specified
- // the unqualified name. We have to concatenate the unqalified name
- // with the domain name.
} else if (label_count == 2) {
- std::ostringstream resp_hostname;
- resp_hostname << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
- opt_hostname_resp->writeString(resp_hostname.str());
+ // If there are two labels, it means that the client has specified
+ // the unqualified name. We have to concatenate the unqalified name
+ // with the domain name.
+ opt_hostname_resp->writeString(d2_mgr.qualifyName(hostname));
}
answer->addOption(opt_hostname_resp);
@@ -902,17 +852,14 @@ Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
// removal request for non-existent hostname.
// - A server has performed reverse, forward or both updates.
// - FQDN data between the new and old lease do not match.
- if ((lease->hostname_ != old_lease->hostname_) ||
- (lease->fqdn_fwd_ != old_lease->fqdn_fwd_) ||
- (lease->fqdn_rev_ != old_lease->fqdn_rev_)) {
+ if (!lease->hasIdenticalFqdn(*old_lease)) {
queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
old_lease);
// If FQDN data from both leases match, there is no need to update.
- } else if ((lease->hostname_ == old_lease->hostname_) &&
- (lease->fqdn_fwd_ == old_lease->fqdn_fwd_) &&
- (lease->fqdn_rev_ == old_lease->fqdn_rev_)) {
+ } else if (lease->hasIdenticalFqdn(*old_lease)) {
return;
+
}
}
@@ -960,9 +907,9 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
void
Dhcpv4Srv::sendNameChangeRequests() {
while (!name_change_reqs_.empty()) {
- // @todo Once next NameChangeRequest is picked from the queue
- // we should send it to the b10-dhcp_ddns module. Currently we
- // just drop it.
+ /// @todo Once next NameChangeRequest is picked from the queue
+ /// we should send it to the b10-dhcp_ddns module. Currently we
+ /// just drop it.
name_change_reqs_.pop();
}
}
@@ -981,8 +928,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// thing this client can get is some global information (like DNS
// servers).
- // perhaps this should be logged on some higher level? This is most likely
- // configuration bug.
+ // perhaps this should be logged on some higher level? This is most
+ // likely configuration bug.
LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
.arg(question->getRemoteAddr().toText())
.arg(serverReceivedPacketName(question->getType()));
@@ -995,7 +942,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// as siaddr has nothing to do with a lease, but otherwise we would have
// to select subnet twice (performance hit) or update too many functions
// at once.
- // @todo: move subnet selection to a common code
+ /// @todo: move subnet selection to a common code
answer->setSiaddr(subnet->getSiaddr());
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
@@ -1038,8 +985,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
(answer->getOption(DHO_HOST_NAME));
if (opt_hostname) {
hostname = opt_hostname->readString();
- // @todo It could be configurable what sort of updates the server
- // is doing when Hostname option was sent.
+ /// @todo It could be configurable what sort of updates the
+ /// server is doing when Hostname option was sent.
fqdn_fwd = true;
fqdn_rev = true;
}
@@ -1049,7 +996,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
- // @todo pass the actual FQDN data.
+ /// @todo pass the actual FQDN data.
Lease4Ptr old_lease;
Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr,
hint, fqdn_fwd, fqdn_rev,
@@ -1074,14 +1021,9 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// generating the entire hostname for the client. The example of the
// client's name, generated from the IP address is: host-192-0-2-3.
if ((fqdn || opt_hostname) && lease->hostname_.empty()) {
- hostname = lease->addr_.toText();
- // Replace dots with hyphens.
- std::replace(hostname.begin(), hostname.end(), '.', '-');
- ostringstream stream;
- // The partial suffix will need to be replaced with the actual
- // domain-name for the client when configuration is implemented.
- stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
- lease->hostname_ = stream.str();
+ lease->hostname_ = CfgMgr::instance()
+ .getD2ClientMgr().generateFqdn(lease->addr_);
+
// The operations below are rather safe, but we want to catch
// any potential exceptions (e.g. invalid lease database backend
// implementation) and log an error.
@@ -1113,22 +1055,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// Subnet mask (type 1)
answer->addOption(getNetmaskOption(subnet));
- // @todo: send renew timer option (T1, option 58)
- // @todo: send rebind timer option (T2, option 59)
+ /// @todo: send renew timer option (T1, option 58)
+ /// @todo: send rebind timer option (T2, option 59)
- // @todo Currently the NameChangeRequests are always generated if
- // real (not fake) allocation is being performed. Should we have
- // control switch to enable/disable NameChangeRequest creation?
- // Perhaps we need a way to detect whether the b10-dhcp-ddns module
- // is up an running?
- if (!fake_allocation) {
+ // Create NameChangeRequests if DDNS is enabled and this is a
+ // real allocation.
+ if (!fake_allocation && CfgMgr::instance().ddnsEnabled()) {
try {
createNameChangeRequests(lease, old_lease);
} catch (const Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_NCR_CREATION_FAILED)
.arg(ex.what());
}
-
}
} else {
@@ -1169,8 +1107,8 @@ Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) {
// address for the response. Instead, we have to check what address our
// socket is bound to and use it as a source address. This operation
// may throw if for some reason the socket is closed.
- // @todo Consider an optimization that we use local address from
- // the query if this address is not broadcast.
+ /// @todo Consider an optimization that we use local address from
+ /// the query if this address is not broadcast.
SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
// Set local adddress, port and interface.
response->setLocalAddr(sock_info.addr_);
@@ -1285,7 +1223,7 @@ Pkt4Ptr
Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
/// @todo Uncomment this (see ticket #3116)
- // sanityCheck(request, MANDATORY);
+ /// sanityCheck(request, MANDATORY);
Pkt4Ptr ack = Pkt4Ptr
(new Pkt4(DHCPACK, request->getTransid()));
@@ -1327,7 +1265,7 @@ void
Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
/// @todo Uncomment this (see ticket #3116)
- // sanityCheck(release, MANDATORY);
+ /// sanityCheck(release, MANDATORY);
// Try to find client-id
ClientIdPtr client_id;
@@ -1352,7 +1290,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
// Does the hardware address match? We don't want one client releasing
// second client's leases.
if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
- // @todo: Print hwaddr from lease as part of ticket #2589
+ /// @todo: Print hwaddr from lease as part of ticket #2589
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
.arg(release->getCiaddr().toText())
.arg(client_id ? client_id->toText() : "(no client-id)")
@@ -1410,9 +1348,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
.arg(client_id ? client_id->toText() : "(no client-id)")
.arg(release->getHWAddr()->toText());
- // Remove existing DNS entries for the lease, if any.
- queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
-
+ if (CfgMgr::instance().ddnsEnabled()) {
+ // Remove existing DNS entries for the lease, if any.
+ queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
+ }
} else {
// Release failed -
LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
@@ -1525,6 +1464,57 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
return (subnet);
}
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
+ // This function is meant to be called internally by the server class, so
+ // we rely on the caller to sanity check the pointer and we don't check
+ // it here.
+
+ // Check if server identifier option is present. If it is not present
+ // we accept the message because it is targetted to all servers.
+ // Note that we don't check cases that server identifier is mandatory
+ // but not present. This is meant to be sanity checked in other
+ // functions.
+ OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ if (!option) {
+ return (true);
+ }
+ // Server identifier is present. Let's convert it to 4-byte address
+ // and try to match with server identifiers used by the server.
+ OptionCustomPtr option_custom =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ // Unable to convert the option to the option type which encapsulates it.
+ // We treat this as non-matching server id.
+ if (!option_custom) {
+ return (false);
+ }
+ // The server identifier option should carry exactly one IPv4 address.
+ // If the option definition for the server identifier doesn't change,
+ // the OptionCustom object should have exactly one IPv4 address and
+ // this check is somewhat redundant. On the other hand, if someone
+ // breaks option it may be better to check that here.
+ if (option_custom->getDataFieldsNum() != 1) {
+ return (false);
+ }
+
+ // The server identifier MUST be an IPv4 address. If given address is
+ // v6, it is wrong.
+ IOAddress server_id = option_custom->readAddress();
+ if (!server_id.isV4()) {
+ return (false);
+ }
+
+ // This function iterates over all interfaces on which the
+ // server is listening to find the one which has a socket bound
+ // to the address carried in the server identifier option.
+ // This has some performance implications. However, given that
+ // typically there will be just a few active interfaces the
+ // performance hit should be acceptable. If it turns out to
+ // be significant, we will have to cache server identifiers
+ // when sockets are opened.
+ return (IfaceMgr::instance().hasOpenSocket(server_id));
+}
+
void
Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
@@ -1602,8 +1592,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
}
// Let's reopen active sockets. openSockets4 will check internally whether
// sockets are marked active or inactive.
- // @todo Optimization: we should not reopen all sockets but rather select
- // those that have been affected by the new configuration.
+ /// @todo Optimization: we should not reopen all sockets but rather select
+ /// those that have been affected by the new configuration.
isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
@@ -1706,5 +1696,86 @@ Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg);
}
+void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+ boost::shared_ptr<OptionString> vendor_class =
+ boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
+
+ string classes = "";
+
+ if (!vendor_class) {
+ return;
+ }
+
+ // DOCSIS specific section
+
+ // Let's keep this as a series of checks. So far we're supporting only
+ // docsis3.0, but there are also docsis2.0, docsis1.1 and docsis1.0. We
+ // may come up with adding several classes, e.g. for docsis2.0 we would
+ // add classes docsis2.0, docsis1.1 and docsis1.0.
+
+ // Also we are using find, because we have at least one traffic capture
+ // where the user class was followed by a space ("docsis3.0 ").
+
+ // For now, the code is very simple, but it is expected to get much more
+ // complex soon. One specific case is that the vendor class is an option
+ // sent by the client, so we should not trust it. To confirm that the device
+ // is indeed a modem, John B. suggested to check whether chaddr field
+ // quals subscriber-id option that was inserted by the relay (CMTS).
+ // This kind of logic will appear here soon.
+ if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+ pkt->addClass(DOCSIS3_CLASS_MODEM);
+ classes += string(DOCSIS3_CLASS_MODEM) + " ";
+ } else
+ if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+ pkt->addClass(DOCSIS3_CLASS_EROUTER);
+ classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+ } else {
+ classes += vendor_class->getValue();
+ pkt->addClass(vendor_class->getValue());
+ }
+
+ if (!classes.empty()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+ .arg(classes);
+ }
+}
+
+bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
+
+ Subnet4Ptr subnet = selectSubnet(query);
+ if (!subnet) {
+ return (true);
+ }
+
+ if (query->inClass(DOCSIS3_CLASS_MODEM)) {
+
+ // Set next-server. This is TFTP server address. Cable modems will
+ // download their configuration from that server.
+ rsp->setSiaddr(subnet->getSiaddr());
+
+ // Now try to set up file field in DHCPv4 packet. We will just copy
+ // content of the boot-file option, which contains the same information.
+ Subnet::OptionDescriptor desc =
+ subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME);
+
+ if (desc.option) {
+ boost::shared_ptr<OptionString> boot =
+ boost::dynamic_pointer_cast<OptionString>(desc.option);
+ if (boot) {
+ std::string filename = boot->getValue();
+ rsp->setFile((const uint8_t*)filename.c_str(), filename.size());
+ }
+ }
+ }
+
+ if (query->inClass(DOCSIS3_CLASS_EROUTER)) {
+
+ // Do not set TFTP server address for eRouter devices.
+ rsp->setSiaddr(IOAddress("0.0.0.0"));
+ }
+
+ return (true);
+}
+
} // namespace dhcp
} // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 324ba4fa0d..ec63e7fcef 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -167,6 +167,21 @@ public:
protected:
+ /// @brief Verifies if the server id belongs to our server.
+ ///
+ /// This function checks if the server identifier carried in the specified
+ /// DHCPv4 message belongs to this server. If the server identifier option
+ /// is absent or the value carried by this option is equal to one of the
+ /// server identifiers used by the server, the true is returned. If the
+ /// server identifier option is present, but it doesn't match any server
+ /// identifier used by this server, the false value is returned.
+ ///
+ /// @param pkt DHCPv4 message which server identifier is to be checked.
+ ///
+ /// @return true, if the server identifier is absent or matches one of the
+ /// server identifiers that the server is using; false otherwise.
+ bool acceptServerId(const Pkt4Ptr& pkt) const;
+
/// @brief verifies if specified packet meets RFC requirements
///
/// Checks if mandatory option is really there, that forbidden option
@@ -247,7 +262,7 @@ protected:
/// using separate options within their respective vendor-option spaces.
///
/// @param question DISCOVER or REQUEST message from a client.
- /// @param msg outgoing message (options will be added here)
+ /// @param answer outgoing message (options will be added here)
void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
/// @brief Assigns a lease and appends corresponding options
@@ -544,6 +559,25 @@ protected:
const std::string& option_space,
isc::dhcp::OptionCollection& options);
+ /// @brief Assigns incoming packet to zero or more classes.
+ ///
+ /// @note For now, the client classification is very simple. It just uses
+ /// content of the vendor-class-identifier option as a class. The resulting
+ /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_ and
+ /// @ref isc::dhcp::Pkt4::inClass).
+ ///
+ /// @param pkt packet to be classified
+ void classifyPacket(const Pkt4Ptr& pkt);
+
+ /// @brief Performs packet processing specific to a class
+ ///
+ /// This processing is a likely candidate to be pushed into hooks.
+ ///
+ /// @param query incoming client's packet
+ /// @param rsp server's response
+ /// @return true if successful, false otherwise (will prevent sending response)
+ bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp);
+
private:
/// @brief Constructs netmask option based on subnet4
diff --git a/src/bin/dhcp4/tests/.gitignore b/src/bin/dhcp4/tests/.gitignore
index 5d14dacb3f..5983892923 100644
--- a/src/bin/dhcp4/tests/.gitignore
+++ b/src/bin/dhcp4/tests/.gitignore
@@ -1 +1,4 @@
/dhcp4_unittests
+/marker_file.h
+/test_data_files_config.h
+/test_libraries.h
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index c72b8b058d..91b5a3b0e8 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -58,7 +58,8 @@ if HAVE_GTEST
# to unexpected errors. For this reason, the --enable-static-link option is
# ignored for unit tests built here.
-lib_LTLIBRARIES = libco1.la libco2.la
+nodistdir=$(abs_top_builddir)/src/bin/dhcp4/tests
+nodist_LTLIBRARIES = libco1.la libco2.la
libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 1d9ccb7fa0..4071adc3fa 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,6 +30,7 @@
#include "marker_file.h"
#include "test_libraries.h"
+#include "test_data_files_config.h"
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
@@ -50,6 +51,23 @@ using namespace std;
namespace {
+/// @brief Prepends the given name with the DHCP4 source directory
+///
+/// @param name file name of the desired file
+/// @return string containing the absolute path of the file in the DHCP source
+/// directory.
+std::string specfile(const std::string& name) {
+ return (std::string(DHCP4_SRC_DIR) + "/" + name);
+}
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+// is valid.
+TEST(Dhcp4SpecTest, basicSpec) {
+ (isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+ ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+}
+
class Dhcp4ParserTest : public ::testing::Test {
public:
Dhcp4ParserTest()
@@ -119,19 +137,19 @@ public:
params["name"] = param_value;
params["space"] = "dhcp4";
params["code"] = "56";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "space") {
params["name"] = "dhcp-message";
params["space"] = param_value;
params["code"] = "56";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "dhcp-message";
params["space"] = "dhcp4";
params["code"] = param_value;
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "dhcp-message";
@@ -143,7 +161,7 @@ public:
params["name"] = "dhcp-message";
params["space"] = "dhcp4";
params["code"] = "56";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
@@ -194,6 +212,62 @@ public:
return (stream.str());
}
+ /// @brief Returns an option from the subnet.
+ ///
+ /// This function returns an option from a subnet to which the
+ /// specified subnet address belongs. The option is identified
+ /// by its code.
+ ///
+ /// @param subnet_address Address which belongs to the subnet from
+ /// which the option is to be returned.
+ /// @param option_code Code of the option to be returned.
+ /// @param expected_options_count Expected number of options in
+ /// the particular subnet.
+ ///
+ /// @return Descriptor of the option. If the descriptor holds a
+ /// NULL option pointer, it means that there was no such option
+ /// in the subnet.
+ Subnet::OptionDescriptor
+ getOptionFromSubnet(const IOAddress& subnet_address,
+ const uint16_t option_code,
+ const uint16_t expected_options_count = 1) {
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address);
+ if (!subnet) {
+ /// @todo replace toText() with the use of operator <<.
+ ADD_FAILURE() << "A subnet for the specified address "
+ << subnet_address.toText()
+ << "does not exist in Config Manager";
+ }
+ Subnet::OptionContainerPtr options =
+ subnet->getOptionDescriptors("dhcp4");
+ if (expected_options_count != options->size()) {
+ ADD_FAILURE() << "The number of options in the subnet '"
+ << subnet_address.toText() << "' is different "
+ " than expected number of options '"
+ << expected_options_count << "'";
+ }
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 1) {
+ ADD_FAILURE() << "There is more than one option having the"
+ " option code '" << option_code << "' in a subnet '"
+ << subnet_address.toText() << "'. Expected "
+ " at most one option";
+ } else if (std::distance(range.first, range.second) == 0) {
+ return (Subnet::OptionDescriptor(OptionPtr(), false));
+ }
+
+ return (*range.first);
+ }
+
/// @brief Test invalid option parameter value.
///
/// This test function constructs the simple configuration
@@ -215,6 +289,24 @@ public:
ASSERT_EQ(1, rcode_);
}
+ /// @brief Test invalid option paramater value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param params Map of parameters defining an option.
+ void
+ testInvalidOptionParam(const std::map<std::string, std::string>& params) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+ }
+
/// @brief Test option against given code and data.
///
/// @param option_desc option descriptor that carries the option to
@@ -256,6 +348,39 @@ public:
expected_data_len));
}
+ /// @brief Test option configuration.
+ ///
+ /// This function creates a configuration for a specified option using
+ /// a map of parameters specified as the argument. The map holds
+ /// name/value pairs which identifies option's configuration parameters:
+ /// - name
+ /// - space
+ /// - code
+ /// - data
+ /// - csv-format.
+ /// This function applies a new server configuration and checks that the
+ /// option being configured is inserted into CfgMgr. The raw contents of
+ /// this option are compared with the binary data specified as expected
+ /// data passed to this function.
+ ///
+ /// @param params Map of parameters defining an option.
+ /// @param option_code Option code.
+ /// @param expected_data Array containing binary data expected to be stored
+ /// in the configured option.
+ /// @param expected_data_len Length of the array holding reference data.
+ void testConfiguration(const std::map<std::string, std::string>& params,
+ const uint16_t option_code,
+ const uint8_t* expected_data,
+ const size_t expected_data_len) {
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+ // The subnet should now hold one option with the specified option code.
+ Subnet::OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
+ ASSERT_TRUE(desc.option);
+ testOption(desc, option_code, expected_data, expected_data_len);
+ }
+
/// @brief Parse and Execute configuration
///
/// Parses a configuration and executes a configuration of the server.
@@ -321,6 +446,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, "
"\"option-def\": [ ], "
"\"option-data\": [ ] }";
static_cast<void>(executeConfiguration(config,
@@ -410,8 +536,195 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
EXPECT_EQ(1000, subnet->getT1());
EXPECT_EQ(2000, subnet->getT2());
EXPECT_EQ(4000, subnet->getValid());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+// Goal of this test is to verify that multiple subnets get unique
+// subnet-ids. Also, test checks that it's possible to do reconfiguration
+// multiple times.
+TEST_F(Dhcp4ParserTest, multipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+ " \"subnet\": \"192.0.5.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ int cnt = 0; // Number of reconfigurations
+
+ do {
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ EXPECT_EQ(2, subnets->at(1)->getID());
+ EXPECT_EQ(3, subnets->at(2)->getID());
+ EXPECT_EQ(4, subnets->at(3)->getID());
+
+ // Repeat reconfiguration process 10 times and check that the subnet-id
+ // is set to the same value. Technically, just two iterations would be
+ // sufficient, but it's nice to have a test that exercises reconfiguration
+ // a bit.
+ } while (++cnt < 10);
}
+// Goal of this test is to verify that a previously configured subnet can be
+// deleted in subsequent reconfiguration.
+TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
+ ConstElementPtr x;
+
+ // All four subnets
+ string config4 = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+ " \"subnet\": \"192.0.5.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Three subnets (the last one removed)
+ string config_first3 = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Second subnet removed
+ string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+ " \"subnet\": \"192.0.4.0/24\" "
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+ " \"subnet\": \"192.0.5.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // CASE 1: Configure 4 subnets, then reconfigure and remove the
+ // last one.
+
+ ElementPtr json = Element::fromJSON(config4);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Do the reconfiguration (the last subnet is removed)
+ json = Element::fromJSON(config_first3);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ subnets = CfgMgr::instance().getSubnets4();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ EXPECT_EQ(2, subnets->at(1)->getID());
+ EXPECT_EQ(3, subnets->at(2)->getID());
+
+ /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+ /// from in between (not first, not last)
+
+#if 0
+ /// @todo: Uncomment subnet removal test as part of #3281.
+ json = Element::fromJSON(config4);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ // Do reconfiguration
+ json = Element::fromJSON(config_second_removed);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ subnets = CfgMgr::instance().getSubnets4();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ // The second subnet (with subnet-id = 2) is no longer there
+ EXPECT_EQ(3, subnets->at(1)->getID());
+ EXPECT_EQ(4, subnets->at(2)->getID());
+#endif
+
+}
+
+/// @todo: implement subnet removal test as part of #3281.
+
// Checks if the next-server defined as global parameter is taken into
// consideration.
TEST_F(Dhcp4ParserTest, nextServerGlobal) {
@@ -1188,7 +1501,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
" \"name\": \"dhcp-message\","
" \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -1261,7 +1574,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
" \"name\": \"dhcp-message\","
" \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -1438,7 +1751,6 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
" } ]"
"}";
-
json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1494,7 +1806,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
" \"name\": \"dhcp-message\","
" \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -1545,6 +1857,89 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
+// The goal of this test is to check that the option carrying a boolean
+// value can be configured using one of the values: "true", "false", "0"
+// or "1".
+TEST_F(Dhcp4ParserTest, optionDataBoolean) {
+ // Create configuration. Use standard option 19 (ip-forwarding).
+ std::map<std::string, std::string> params;
+ params["name"] = "ip-forwarding";
+ params["space"] = "dhcp4";
+ params["code"] = "19";
+ params["data"] = "true";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
+ " boolean value"));
+
+ // The subnet should now hold one option with the code 19.
+ Subnet::OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"),
+ 19);
+ ASSERT_TRUE(desc.option);
+
+ // This option should be set to "true", represented as 0x1 in the option
+ // buffer.
+ uint8_t expected_option_data[] = {
+ 0x1
+ };
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "1" value. This should have the same
+ // effect as if "true" was specified.
+ params["data"] = "1";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The value of "1" with a few leading zeros should work too.
+ params["data"] = "00001";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "false" value.
+ params["data"] = "false";
+ // The option buffer should now hold the value of 0.
+ expected_option_data[0] = 0;
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Specifying "0" should have the same effect as "false".
+ params["data"] = "0";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The same effect should be for multiple 0 chars.
+ params["data"] = "00000";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Bogus values should not be accepted.
+ params["data"] = "bugus";
+ testInvalidOptionParam(params);
+
+ params["data"] = "2";
+ testInvalidOptionParam(params);
+
+ // Now let's test that it is possible to use binary format.
+ params["data"] = "0";
+ params["csv-format"] = "false";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The binary 1 should work as well.
+ params["data"] = "1";
+ expected_option_data[0] = 1;
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+ // As well as an even number of digits.
+ params["data"] = "01";
+ testConfiguration(params, 19, expected_option_data,
+ sizeof(expected_option_data));
+
+}
+
// Goal of this test is to verify options configuration
// for multiple subnets.
TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
@@ -1622,6 +2017,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
}
+
+
// Verify that empty option name is rejected in the configuration.
TEST_F(Dhcp4ParserTest, optionNameEmpty) {
// Empty option names not allowed.
@@ -1672,14 +2069,6 @@ TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
testInvalidOptionParam("0x0102", "data");
}
-// Verify that option data consisting od an odd number of
-// hexadecimal digits is rejected in the configuration.
-TEST_F(Dhcp4ParserTest, optionDataOddLength) {
- // Option code 0 is reserved and should not be accepted
- // by configuration parser.
- testInvalidOptionParam("123", "data");
-}
-
// Verify that either lower or upper case characters are allowed
// to specify the option data.
TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
@@ -1995,7 +2384,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
" \"name\": \"option-one\","
" \"space\": \"vendor-4491\"," // VENDOR_ID_CABLE_LABS = 4491
" \"code\": 100," // just a random code
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -2127,7 +2516,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
" \"name\": \"dhcp-message\","
" \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -2299,6 +2688,113 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
}
+// This test checks the ability of the server to parse a configuration
+// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, d2ClientConfig) {
+ ConstElementPtr status;
+
+ // Verify that the D2 configuraiton can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ string config_str = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.168.2.1\", "
+ " \"server-port\" : 777, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"allow-client-update\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" },"
+ "\"valid-lifetime\": 4000 }";
+ // Convert the JSON string to configuration elements.
+ ElementPtr config;
+ ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+ // Pass the configuration in for parsing.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Verify that DHCP-DDNS updating is enabled.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Verify that the D2 configuration can be retrieved.
+ d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are correct.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(777, d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+ EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
+ EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+ EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+ EXPECT_TRUE(d2_client_config->getReplaceClientName());
+ EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+ EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+}
+
+// This test checks the ability of the server to handle a configuration
+// containing an invalid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
+ ConstElementPtr status;
+
+ // Configuration string with an invalid D2 client config,
+ // "server-ip" is missing.
+ string config_str = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ " \"dhcp-ddns\" : {"
+ " \"enable-updates\" : true, "
+ " \"server-port\" : 5301, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"allow-client-update\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" },"
+ "\"valid-lifetime\": 4000 }";
+
+ // Convert the JSON string to configuration elements.
+ ElementPtr config;
+ ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+ // Configuration should not throw, but should fail.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+
+ // check if returned status is failed.
+ checkResult(status, 1);
+
+ // Verify that the D2 configuraiton can be fetched and is set to disabled.
+ D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify that the convenience method agrees.
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+}
}
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 5142c70c06..e930ba0316 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -584,7 +584,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) {
// lifetime is correct, that T1 and T2 are returned properly
checkAddressParams(offer, subnet_);
- EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+ EXPECT_EQ(offer->getYiaddr(), hint);
// Check identifiers
checkServerId(offer, srv->getServerID());
@@ -624,7 +624,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) {
// lifetime is correct, that T1 and T2 are returned properly
checkAddressParams(offer, subnet_);
- EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+ EXPECT_EQ(offer->getYiaddr(), hint);
// Check identifiers
checkServerId(offer, srv->getServerID());
@@ -664,7 +664,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) {
// lifetime is correct, that T1 and T2 are returned properly
checkAddressParams(offer, subnet_);
- EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+ EXPECT_NE(offer->getYiaddr(), hint);
// Check identifiers
checkServerId(offer, srv->getServerID());
@@ -735,12 +735,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) {
checkClientId(offer3, clientid3);
// Finally check that the addresses offered are different
- EXPECT_NE(addr1.toText(), addr2.toText());
- EXPECT_NE(addr2.toText(), addr3.toText());
- EXPECT_NE(addr3.toText(), addr1.toText());
- cout << "Offered address to client1=" << addr1.toText() << endl;
- cout << "Offered address to client2=" << addr2.toText() << endl;
- cout << "Offered address to client3=" << addr3.toText() << endl;
+ EXPECT_NE(addr1, addr2);
+ EXPECT_NE(addr2, addr3);
+ EXPECT_NE(addr3, addr1);
+ cout << "Offered address to client1=" << addr1 << endl;
+ cout << "Offered address to client2=" << addr2 << endl;
+ cout << "Offered address to client3=" << addr3 << endl;
}
// Checks whether echoing back client-id is controllable, i.e.
@@ -802,7 +802,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) {
// Check if we get response at all
checkResponse(ack, DHCPACK, 1234);
- EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+ EXPECT_EQ(hint, ack->getYiaddr());
// Check that address was returned from proper range, that its lease
// lifetime is correct, that T1 and T2 are returned properly
@@ -886,9 +886,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
IOAddress addr3 = ack3->getYiaddr();
// Check that every client received the address it requested
- EXPECT_EQ(req_addr1.toText(), addr1.toText());
- EXPECT_EQ(req_addr2.toText(), addr2.toText());
- EXPECT_EQ(req_addr3.toText(), addr3.toText());
+ EXPECT_EQ(req_addr1, addr1);
+ EXPECT_EQ(req_addr2, addr2);
+ EXPECT_EQ(req_addr3, addr3);
// Check that the assigned address is indeed from the configured pool
checkAddressParams(ack1, subnet_);
@@ -910,12 +910,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
// Finally check that the addresses offered are different
- EXPECT_NE(addr1.toText(), addr2.toText());
- EXPECT_NE(addr2.toText(), addr3.toText());
- EXPECT_NE(addr3.toText(), addr1.toText());
- cout << "Offered address to client1=" << addr1.toText() << endl;
- cout << "Offered address to client2=" << addr2.toText() << endl;
- cout << "Offered address to client3=" << addr3.toText() << endl;
+ EXPECT_NE(addr1, addr2);
+ EXPECT_NE(addr2, addr3);
+ EXPECT_NE(addr3, addr1);
+ cout << "Offered address to client1=" << addr1 << endl;
+ cout << "Offered address to client2=" << addr2 << endl;
+ cout << "Offered address to client3=" << addr3 << endl;
}
// Checks whether echoing back client-id is controllable
@@ -1003,7 +1003,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
// Check if we get response at all
checkResponse(ack, DHCPACK, 1234);
- EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+ EXPECT_EQ(addr, ack->getYiaddr());
// Check that address was returned from proper range, that its lease
// lifetime is correct, that T1 and T2 are returned properly
@@ -1031,6 +1031,56 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
+// This test verifies that the logic which matches server identifier in the
+// received message with server identifiers used by a server works correctly:
+// - a message with no server identifier is accepted,
+// - a message with a server identifier which matches one of the server
+// identifiers used by a server is accepted,
+// - a message with a server identifier which doesn't match any server
+// identifier used by a server, is not accepted.
+TEST_F(Dhcpv4SrvFakeIfaceTest, acceptServerId) {
+ NakedDhcpv4Srv srv(0);
+
+ Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
+ // If no server identifier option is present, the message is always
+ // accepted.
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Create definition of the server identifier option.
+ OptionDefinition def("server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
+ "ipv4-address", false);
+
+ // Add a server identifier option which doesn't match server ids being
+ // used by the server. The accepted server ids are the IPv4 addresses
+ // configured on the interfaces. The 10.1.2.3 is not configured on
+ // any interfaces.
+ OptionCustomPtr other_serverid(new OptionCustom(def, Option::V6));
+ other_serverid->writeAddress(IOAddress("10.1.2.3"));
+ pkt->addOption(other_serverid);
+ EXPECT_FALSE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on eth0 interface.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr eth0_serverid(new OptionCustom(def, Option::V6));
+ eth0_serverid->writeAddress(IOAddress("192.0.3.1"));
+ ASSERT_NO_THROW(pkt->addOption(eth0_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+ // Remove the server identifier.
+ ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // Add a server id being an IPv4 address configured on eth1 interface.
+ // A DHCPv4 message holding this server identifier should be accepted.
+ OptionCustomPtr eth1_serverid(new OptionCustom(def, Option::V6));
+ eth1_serverid->writeAddress(IOAddress("10.0.0.1"));
+ ASSERT_NO_THROW(pkt->addOption(eth1_serverid));
+ EXPECT_TRUE(srv.acceptServerId(pkt));
+
+}
+
// @todo: Implement tests for rejecting renewals
// This test verifies if the sanityCheck() really checks options presence.
@@ -3123,8 +3173,32 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsisDefinitions) {
ASSERT_EQ(0, rcode_);
}
+// Checks if client packets are classified properly
+TEST_F(Dhcpv4SrvTest, clientClassification) {
+
+ NakedDhcpv4Srv srv(0);
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // vendor-class set to docsis3.0
+ Pkt4Ptr dis1;
+ ASSERT_NO_THROW(dis1 = captureRelayedDiscover());
+ ASSERT_NO_THROW(dis1->unpack());
+
+ srv.classifyPacket(dis1);
+
+ EXPECT_TRUE(dis1->inClass("docsis3.0"));
+ EXPECT_FALSE(dis1->inClass("eRouter1.0"));
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // vendor-class set to eRouter1.0
+ Pkt4Ptr dis2;
+ ASSERT_NO_THROW(dis2 = captureRelayedDiscover2());
+ ASSERT_NO_THROW(dis2->unpack());
+
+ srv.classifyPacket(dis2);
+
+ EXPECT_TRUE(dis2->inClass("eRouter1.0"));
+ EXPECT_FALSE(dis2->inClass("docsis3.0"));
}
- /*I}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace */
+}; // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
index 8f96c1e23c..e983e7b939 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
@@ -126,7 +126,7 @@ void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
// Check that something is offered
- EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
+ EXPECT_NE("0.0.0.0", a->getYiaddr().toText());
}
::testing::AssertionResult
@@ -299,14 +299,14 @@ Lease4Ptr Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp,
Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
if (!lease) {
- cout << "Lease for " << expected_addr.toText()
+ cout << "Lease for " << expected_addr
<< " not found in the database backend.";
return (Lease4Ptr());
}
- EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+ EXPECT_EQ(rsp->getYiaddr(), expected_addr);
- EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(expected_addr, lease->addr_);
if (client_id) {
EXPECT_TRUE(*lease->client_id_ == *id);
}
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index ebb6300a5d..8a2211789e 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -222,7 +222,8 @@ public:
/// @brief returns captured DISCOVER that went through a relay
///
- /// See method code for a detailed explanation.
+ /// See method code for a detailed explanation. This is a discover from
+ /// docsis3.0 device (Cable Modem)
///
/// @return relayed DISCOVER
Pkt4Ptr captureRelayedDiscover();
@@ -253,6 +254,13 @@ public:
createPacketFromBuffer(const Pkt4Ptr& src_pkt,
Pkt4Ptr& dst_pkt);
+ /// @brief returns captured DISCOVER that went through a relay
+ ///
+ /// See method code for a detailed explanation. This is a discover from
+ /// eRouter1.0 device (CPE device integrated with cable modem)
+ ///
+ /// @return relayed DISCOVER
+ Pkt4Ptr captureRelayedDiscover2();
/// @brief generates a DHCPv4 packet based on provided hex string
///
@@ -438,10 +446,12 @@ public:
using Dhcpv4Srv::processClientName;
using Dhcpv4Srv::computeDhcid;
using Dhcpv4Srv::createNameChangeRequests;
+ using Dhcpv4Srv::acceptServerId;
using Dhcpv4Srv::sanityCheck;
using Dhcpv4Srv::srvidToString;
using Dhcpv4Srv::unpackOptions;
using Dhcpv4Srv::name_change_reqs_;
+ using Dhcpv4Srv::classifyPacket;
};
}; // end of isc::dhcp::test namespace
diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc
index d3bf9ae2c1..e17a7e3ac6 100644
--- a/src/bin/dhcp4/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp4/tests/fqdn_unittest.cc
@@ -18,6 +18,7 @@
#include <dhcp/option_int_array.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/cfgmgr.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
@@ -32,13 +33,53 @@ namespace {
class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
public:
+
+ // Bit Constants for turning on and off DDNS configuration options.
+ static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
+ static const uint16_t OVERRIDE_NO_UPDATE = 2;
+ static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
+ static const uint16_t REPLACE_CLIENT_NAME = 8;
+
NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
srv_ = new NakedDhcpv4Srv(0);
+ // Config DDNS to be enabled, all controls off
+ enableD2();
}
+
virtual ~NameDhcpv4SrvTest() {
delete srv_;
}
+ /// @brief Sets the server's DDNS configuration to ddns updates disabled.
+ void disableD2() {
+ // Default constructor creates a config with DHCP-DDNS updates
+ // disabled.
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ /// @brief Enables DHCP-DDNS updates with the given options enabled.
+ ///
+ /// Replaces the current D2ClientConfiguration with a configuration
+ /// which as updates enabled and the control options set based upon
+ /// the bit mask of options.
+ ///
+ /// @param mask Bit mask of configuration options that should be enabled.
+ void enableD2(const uint16_t mask = 0) {
+ D2ClientConfigPtr cfg;
+
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("192.0.2.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ (mask & ALWAYS_INCLUDE_FQDN),
+ (mask & OVERRIDE_NO_UPDATE),
+ (mask & OVERRIDE_CLIENT_UPDATE),
+ (mask & REPLACE_CLIENT_NAME),
+ "myhost", "example.com")));
+
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
// Create a lease to be used by various tests.
Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
const std::string& hostname,
@@ -78,15 +119,18 @@ public:
return (opt_hostname);
}
- // Generates partial hostname from the address. The format of the
- // generated address is: host-A-B-C-D, where A.B.C.D is an IP
- // address.
+ /// @brief Convenience method for generating an FQDN from an IP address.
+ ///
+ /// This is just a wrapper method around the D2ClientMgr's method for
+ /// generating domain names from the configured prefix, suffix, and a
+ /// given IP address. This is useful for verifying that fully generated
+ /// names are correct.
+ ///
+ /// @param addr IP address used in the lease.
+ ///
+ /// @return An std::string contained the generated FQDN.
std::string generatedNameFromAddress(const IOAddress& addr) {
- std::string gen_name = addr.toText();
- std::replace(gen_name.begin(), gen_name.end(), '.', '-');
- std::ostringstream hostname;
- hostname << "host-" << gen_name;
- return (hostname.str());
+ return(CfgMgr::instance().getD2ClientMgr().generateFqdn(addr));
}
// Get the Client FQDN Option from the given message.
@@ -182,6 +226,21 @@ public:
Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
ASSERT_TRUE(fqdn);
+ checkFqdnFlags(answer, exp_flags);
+
+ EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
+ EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
+
+ }
+
+ /// @brief Checks the packet's FQDN option flags against a given mask
+ ///
+ /// @param pkt IPv4 packet whose FQDN flags should be checked.
+ /// @param exp_flags Bit mask of flags that are expected to be true.
+ void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) {
+ Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt);
+ ASSERT_TRUE(fqdn);
+
const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
@@ -191,12 +250,9 @@ public:
EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
-
- EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
- EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
-
}
+
// Processes the Hostname option in the client's message and returns
// the hostname option which would be sent to the client. It will
// throw NULL pointer if the hostname option is not to be included
@@ -221,8 +277,8 @@ public:
}
- // Test that the client message holding an FQDN is processed and the
- // NameChangeRequests are generated.
+ // Test that the client message holding an FQDN is processed and
+ // that the response packet is as expected.
void testProcessMessageWithFqdn(const uint8_t msg_type,
const std::string& hostname) {
Pkt4Ptr req = generatePktWithFqdn(msg_type, Option4ClientFqdn::FLAG_S |
@@ -287,6 +343,50 @@ public:
srv_->name_change_reqs_.pop();
}
+
+ /// @brief Tests processing a request with the given client flags
+ ///
+ /// This method creates a request with its FQDN flags set to the given
+ /// value and submits it to the server for processing. It then checks
+ /// the following:
+ /// 1. Did the server generate an ACK with the correct FQDN flags
+ /// 2. If the server should have generated an NCR, did it? and If
+ /// so was it correct?
+ ///
+ /// @param client_flags Mask of client FQDN flags which are true
+ /// @param response_flags Mask of expected FQDN flags in the response
+ void flagVsConfigScenario(const uint8_t client_flags,
+ const uint8_t response_flags) {
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+
+ // Process the request.
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ // Verify the response and flags.
+ checkResponse(reply, DHCPACK, 1234);
+ checkFqdnFlags(reply, response_flags);
+
+ // There should be an NCR only if response S flag is 1.
+ /// @todo This logic will need to change if forward and reverse
+ /// updates are ever controlled independently.
+ if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) {
+ ASSERT_EQ(0, srv_->name_change_reqs_.size());
+ } else {
+ // Verify that there is one NameChangeRequest generated as expected.
+ ASSERT_EQ(1, srv_->name_change_reqs_.size());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ reply->getYiaddr().toText(),
+ "myhost.example.com.",
+ "", // empty DHCID means don't check it
+ time(NULL) + subnet_->getValid(),
+ subnet_->getValid(), true);
+ }
+ }
+
+
NakedDhcpv4Srv* srv_;
};
@@ -348,21 +448,101 @@ TEST_F(NameDhcpv4SrvTest, dhcidComputeFromHWAddr) {
EXPECT_EQ(dhcid_ref, dhcid.toStr());
}
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off
+// - Client requests forward update (N = 0, S = 1)
+//
+// Server should perform the update:
+// - Reponse flags should N = 0, S = 1, O = 0
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, updatesEnabled) {
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S));
+}
-// Test that server confirms to perform the forward and reverse DNS update,
-// when client asks for it.
-TEST_F(NameDhcpv4SrvTest, serverUpdateForwardFqdn) {
- Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
- Option4ClientFqdn::FLAG_E |
- Option4ClientFqdn::FLAG_S,
- "myhost.example.com.",
- Option4ClientFqdn::FULL,
- true);
+// Tests the following scenario
+// - Updates are disabled
+// - Client requests forward update (N = 0, S = 1)
+//
+// Server should NOT perform updates:
+// - Response flags should N = 1, S = 0, O = 1
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, updatesDisabled) {
+ disableD2();
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_O));
+}
- testProcessFqdn(query,
- Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
- "myhost.example.com.");
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off.
+// - Client requests no updates (N = 1, S = 0)
+//
+// Server should NOT perform updates:
+// - Response flags should N = 1, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectNoUpdate) {
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N));
+}
+// Tests the following scenario:
+// - Updates are enabled
+// - override-no-update is on
+// - Client requests no updates (N = 1, S = 0)
+//
+// Server should override "no update" request and perform updates:
+// - Response flags should be N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) {
+ enableD2(OVERRIDE_NO_UPDATE);
+ flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N),
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O));
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - All overrides are off.
+// - Client requests delegation (N = 0, S = 0)
+//
+// Server should respect client's delegation request and NOT do updates:
+
+// - Response flags should be N = 1, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectClientDelegation) {
+
+ flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_N));
+}
+
+// Tests the following scenario:
+// - Updates are enabled
+// - override-client-update is on.
+// - Client requests delegation (N = 0, S = 0)
+//
+// Server should override client's delegation request and do updates:
+// - Response flags should be N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) {
+ // Turn on override-client-update.
+ enableD2(OVERRIDE_CLIENT_UPDATE);
+
+ flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+ (Option4ClientFqdn::FLAG_E |
+ Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O));
}
// Test that server processes the Hostname option sent by a client and
@@ -447,34 +627,6 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
}
-// Test server's response when client requests no DNS update.
-TEST_F(NameDhcpv4SrvTest, noUpdateFqdn) {
- Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
- Option4ClientFqdn::FLAG_E |
- Option4ClientFqdn::FLAG_N,
- "myhost.example.com.",
- Option4ClientFqdn::FULL,
- true);
- testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
- Option4ClientFqdn::FLAG_N,
- "myhost.example.com.");
-}
-
-// Test that server does not accept delegation of the forward DNS update
-// to a client.
-TEST_F(NameDhcpv4SrvTest, clientUpdateNotAllowedFqdn) {
- Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
- Option4ClientFqdn::FLAG_E,
- "myhost.example.com.",
- Option4ClientFqdn::FULL,
- true);
-
- testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
- Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_O,
- "myhost.example.com.");
-
-}
-
// Test that exactly one NameChangeRequest is generated when the new lease
// has been acquired (old lease is NULL).
TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
@@ -600,18 +752,42 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
// Verify that there is one NameChangeRequest generated.
ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
// The hostname is generated from the IP address acquired (yiaddr).
- std::ostringstream hostname;
- hostname << generatedNameFromAddress(reply->getYiaddr())
- << ".example.com.";
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
- reply->getYiaddr().toText(), hostname.str(),
+ reply->getYiaddr().toText(), hostname,
"", // empty DHCID forces that it is not checked
time(NULL) + subnet_->getValid(),
subnet_->getValid(), true);
}
// Test that server generates client's hostname from the IP address assigned
+// to it when DHCPv4 Client FQDN option specifies an empty domain-name AND
+// ddns updates are disabled.
+TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+ disableD2();
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "", Option4ClientFqdn::PARTIAL, true);
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+ checkResponse(reply, DHCPACK, 1234);
+
+ Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply);
+ ASSERT_TRUE(fqdn);
+
+ // The hostname is generated from the IP address acquired (yiaddr).
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+ EXPECT_EQ(hostname, fqdn->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType());
+}
+
+
+// Test that server generates client's hostname from the IP address assigned
// to it when Hostname option carries the top level domain-name.
TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
@@ -626,11 +802,12 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
// Verify that there is one NameChangeRequest generated.
ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
// The hostname is generated from the IP address acquired (yiaddr).
- std::ostringstream hostname;
- hostname << generatedNameFromAddress(reply->getYiaddr()) << ".example.com.";
+ std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
- reply->getYiaddr().toText(), hostname.str(),
+ reply->getYiaddr().toText(), hostname,
"", // empty DHCID forces that it is not checked
time(NULL), subnet_->getValid(), true);
}
@@ -742,21 +919,23 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
time(NULL), subnet_->getValid(), true);
}
-// Test that when the Release message is sent for the previously acquired
-// lease, then server genenerates a NameChangeRequest to remove the entries
-// corresponding to the lease being released.
+// Test that when a release message is sent for a previously acquired lease,
+// DDNS updates are enabled that the server genenerates a NameChangeRequest
+// to remove entries corresponding to the released lease.
TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+ // Verify the updates are enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Create and process a lease request so we have a lease to release.
Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
Option4ClientFqdn::FLAG_E,
"myhost.example.com.",
Option4ClientFqdn::FULL, true);
-
Pkt4Ptr reply;
ASSERT_NO_THROW(reply = srv_->processRequest(req));
-
checkResponse(reply, DHCPACK, 1234);
- // Verify that there is one NameChangeRequest generated.
+ // Verify that there is one NameChangeRequest generated for lease.
ASSERT_EQ(1, srv_->name_change_reqs_.size());
verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
reply->getYiaddr().toText(), "myhost.example.com.",
@@ -764,18 +943,57 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
"965B68B6D438D98E680BF10B09F3BCF",
time(NULL), subnet_->getValid(), true);
- // Create a Release message.
+ // Create and process the Release message.
Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
rel->setCiaddr(reply->getYiaddr());
rel->setRemoteAddr(IOAddress("192.0.2.3"));
rel->addOption(generateClientId());
rel->addOption(srv_->getServerID());
-
ASSERT_NO_THROW(srv_->processRelease(rel));
// The lease has been removed, so there should be a NameChangeRequest to
// remove corresponding DNS entries.
ASSERT_EQ(1, srv_->name_change_reqs_.size());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ reply->getYiaddr().toText(), "myhost.example.com.",
+ "00010132E91AA355CFBB753C0F0497A5A940436"
+ "965B68B6D438D98E680BF10B09F3BCF",
+ time(NULL), subnet_->getValid(), true);
+}
+
+// Test that when the Release message is sent for a previously acquired lease
+// and DDNS updates are disabled that server does NOT generate a
+// NameChangeRequest to remove entries corresponding to the released lease.
+TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+ // Disable DDNS.
+ disableD2();
+ ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ // Create and process a lease request so we have a lease to release.
+ Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL, true);
+ Pkt4Ptr reply;
+ ASSERT_NO_THROW(reply = srv_->processRequest(req));
+ checkResponse(reply, DHCPACK, 1234);
+
+ // With DDNS updates disabled, there should be not be a NameChangeRequest
+ // for the add.
+ ASSERT_EQ(0, srv_->name_change_reqs_.size());
+
+ // Create and process the Release message.
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setCiaddr(reply->getYiaddr());
+ rel->setRemoteAddr(IOAddress("192.0.2.3"));
+ rel->addOption(generateClientId());
+ rel->addOption(srv_->getServerID());
+ ASSERT_NO_THROW(srv_->processRelease(rel));
+
+ // With DDNS updates disabled, there should be not be a NameChangeRequest
+ // for the remove.
+ ASSERT_EQ(0, srv_->name_change_reqs_.size());
}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/test_data_files_config.h.in b/src/bin/dhcp4/tests/test_data_files_config.h.in
new file mode 100644
index 0000000000..0b778cf8a8
--- /dev/null
+++ b/src/bin/dhcp4/tests/test_data_files_config.h.in
@@ -0,0 +1,17 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file
+/// can find it reliably.
+#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4"
diff --git a/src/bin/dhcp4/tests/wireshark.cc b/src/bin/dhcp4/tests/wireshark.cc
index 80b4737083..c07bc1bc29 100644
--- a/src/bin/dhcp4/tests/wireshark.cc
+++ b/src/bin/dhcp4/tests/wireshark.cc
@@ -71,7 +71,10 @@ void Dhcpv4SrvTest::captureSetDefaultFields(const Pkt4Ptr& pkt) {
Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover() {
-/* string exported from Wireshark:
+/* This is packet 1 from capture
+ dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
Source port: bootps (67)
@@ -98,7 +101,7 @@ Bootstrap Protocol
Magic cookie: DHCP
Option: (53) DHCP Message Type
Option: (55) Parameter Request List
- Option: (60) Vendor class identifier
+ Option: (60) Vendor class identifier (docsis3.0)
Option: (125) V-I Vendor-specific Information
- suboption 1 (Option Request): requesting option 2
- suboption 5 (Modem Caps): 117 bytes
@@ -129,6 +132,59 @@ Bootstrap Protocol
return (packetFromCapture(hex_string));
}
+Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover2() {
+
+/* This is packet 5 from capture
+ dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
+
+User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
+Bootstrap Protocol
+ Message type: Boot Request (1)
+ Hardware type: Ethernet (0x01)
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x5d05478f
+ Seconds elapsed: 5
+ Bootp flags: 0x0000 (Unicast)
+ Client IP address: 0.0.0.0 (0.0.0.0)
+ Your (client) IP address: 0.0.0.0 (0.0.0.0)
+ Next server IP address: 0.0.0.0 (0.0.0.0)
+ Relay agent IP address: 10.254.226.1 (10.254.226.1)
+ Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type
+ Option: (55) Parameter Request List
+ Option: (43) Vendor-Specific Information
+ Option: (60) Vendor class identifier (eRouter1.0)
+ Option: (15) Domain Name
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option
+ Option: (255) End */
+
+ string hex_string =
+ "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000063825363350101370e"
+ "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55"
+ "544552040d324252323239553430303434430504312e3034060856312e33332e303307"
+ "07322e332e305232080630303039354209094347333030304443520a074e6574676561"
+ "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d"
+ "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420"
+ "000002020620e52ab8151409090000118b0401020300ff";
+
+ return (packetFromCapture(hex_string));
+}
+
}; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/.gitignore b/src/bin/dhcp6/.gitignore
index 58781893aa..dae9039523 100644
--- a/src/bin/dhcp6/.gitignore
+++ b/src/bin/dhcp6/.gitignore
@@ -4,3 +4,4 @@
/dhcp6_messages.h
/spec_config.h
/spec_config.h.pre
+/s-messages
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 1bb3da0421..32105ad074 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -497,12 +497,12 @@ protected:
"parser error: interface (defined for locally reachable "
"subnets) and interface-id (defined for subnets reachable"
" via relays) cannot be defined at the same time for "
- "subnet " << addr.toText() << "/" << (int)len);
+ "subnet " << addr << "/" << (int)len);
}
}
stringstream tmp;
- tmp << addr.toText() << "/" << static_cast<int>(len)
+ tmp << addr << "/" << static_cast<int>(len)
<< " with params t1=" << t1 << ", t2=" << t2 << ", pref="
<< pref << ", valid=" << valid;
@@ -646,6 +646,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
DHCP6_CONFIG_START).arg(config_set->str());
+ // Before starting any subnet operations, let's reset the subnet-id counter,
+ // so newly recreated configuration starts with first subnet-id equal 1.
+ Subnet::resetSubnetID();
+
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet6 structure
// depend on the global values. Also, option values configuration
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index e42c83bd75..43497cdb89 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -227,7 +227,7 @@ void ControlledDhcpv6Srv::establishSession() {
int ctrl_socket = cc_session_->getSocketDesc();
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
.arg(ctrl_socket);
- IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
+ IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
}
void ControlledDhcpv6Srv::disconnectSession() {
@@ -236,13 +236,16 @@ void ControlledDhcpv6Srv::disconnectSession() {
config_session_ = NULL;
}
if (cc_session_) {
+
+ int ctrl_socket = cc_session_->getSocketDesc();
cc_session_->disconnect();
+
+ // deregister session socket
+ IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
+
delete cc_session_;
cc_session_ = NULL;
}
-
- // deregister session socket
- IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
}
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index ffd43c3a2e..62c1813cb6 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -105,7 +105,8 @@ protected:
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
- /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ /// be later accessed with
+ /// \ref isc::config::ConfigData::getFullConfig().
///
/// @param new_config new configuration.
///
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index c0284c719b..c722472b27 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -190,6 +190,33 @@ implemented within the context of the server and it has access to all objects
which define its configuration (including dynamically created option
definitions).
+@section dhcpv6Classifier DHCPv6 Client Classification
+
+Kea DHCPv6 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (16) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv6Srv::classifyPacket() method is
+called. It attempts to extract content of the vendor class option and interprets
+as a name of the class. Although the RFC3315 says that the vendor class may
+contain more than one chunk of data, the existing code handles only one data
+block, because that is what actual devices use. For now, the code has been
+tested with two classes used in cable modem networks: eRouter1.0 and docsis3.0,
+but any other content of the vendor class option will be interpreted as a class
+name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt6::inClass method should
+be used.
+
+Currently there is no class behaviour coded in DHCPv6, hence no v6 equivalent of
+@ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
+it will be conducted in an external hooks library.
+
@section dhcpv6Other Other DHCPv6 topics
For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 7e8b64ed39..a871a58a30 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +27,10 @@ successfully established a session with the BIND 10 control channel.
This debug message is issued just before the IPv6 DHCP server attempts
to establish a session with the BIND 10 control channel.
+% DHCP6_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
+
% DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
This error message indicates that the received message is being dropped
because it does not include the mandatory client-id option necessary for
@@ -100,13 +104,7 @@ in its response to the client.
This debug message is logged when server has found the DHCPv6 Client FQDN Option
sent by a client and started processing it.
-% DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME FQDN for the lease being deleted is empty: %1
-This error message is issued when a lease being deleted contains an indication
-that the DNS Update has been performed for it, but the FQDN is missing for this
-lease. This is an indication that someone may have messed up in the lease
-database.
-
-% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME FQDN for the lease being deleted has invalid format: %1
+% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME invalid FQDN: %1 for the lease: %2 when removing DNS bindings
This error message is issued when a lease being deleted contains an indication
that the DNS Update has been performed for it, but the FQDN held in the lease
database has invalid format and can't be transformed to the canonical on-wire
@@ -233,6 +231,11 @@ but the lease does not contain any client identification. This is most
likely due to a software error: please raise a bug report. As a temporary
workaround, manually remove the lease entry from the database.
+% DHCP6_NAME_GEN_UPDATE_FAIL failed to update the lease using address %1, after generating FQDN for a client, reason: %2
+This message indicates the failure when trying to update the lease and/or
+options in the server's response with the hostname generated by the server
+from the acquired address.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -268,6 +271,10 @@ of packet. Note that a packet marked as UNKNOWN may well be a valid
DHCP packet, just a type not expected by the server (e.g. it will report
a received OFFER packet as UNKNOWN).
+% DHCP6_PACKET_MISMATCH_SERVERID_DROP dropping packet %1 (transid=%2, interface=%3) having mismatched server identifier
+A debug message noting that server has received message with server identifier
+option that not matching server identifier that server is using.
+
% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv6 DHCP server tried to receive a packet but an error
occurred during this attempt. The reason for the error is included in
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 9b2b2c448b..391bff6c47 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -104,9 +104,6 @@ namespace {
// DHCPv6 Client FQDN Option sent by a client. They will be removed
// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
-// Should server always include the FQDN option in its response, regardless
-// if it has been requested in ORO (Disabled).
-const bool FQDN_ALWAYS_INCLUDE = false;
// Enable AAAA RR update delegation to the client (Disabled).
const bool FQDN_ALLOW_CLIENT_UPDATE = false;
// Globally enable updates (Enabled).
@@ -211,6 +208,33 @@ void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
IfaceMgr::instance().send(packet);
}
+bool
+Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt){
+ /// @todo Currently we always check server identifier regardless if
+ /// it is allowed in the received message or not (per RFC3315).
+ /// If the server identifier is not allowed in the message, the
+ /// sanityCheck function should deal with it. We may rethink this
+ /// design if we decide that it is appropriate to check at this stage
+ /// of message processing that the server identifier must or must not
+ /// be present. In such case however, the logic checking server id
+ /// will have to be removed from sanityCheck and placed here instead,
+ /// to avoid duplicate checks.
+ OptionPtr server_id = pkt->getOption(D6O_SERVERID);
+ if (server_id){
+ // Let us test received ServerID if it is same as ServerID
+ // which is beeing used by server
+ if (getServerID()->getData() != server_id->getData()){
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
+ .arg(pkt->getName())
+ .arg(pkt->getTransid())
+ .arg(pkt->getIface());
+ return (false);
+ }
+ }
+ // retun True if: no serverid received or ServerIDs matching
+ return (true);
+}
+
bool Dhcpv6Srv::run() {
while (!shutdown_) {
/// @todo Calculate actual timeout to the next event (e.g. lease
@@ -283,6 +307,12 @@ bool Dhcpv6Srv::run() {
continue;
}
}
+ // Check if received query carries server identifier matching
+ // server identifier being used by the server.
+ if (!testServerID(query)){
+ continue;
+ }
+
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
.arg(query->getName());
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
@@ -316,6 +346,9 @@ bool Dhcpv6Srv::run() {
callout_handle->getArgument("query6", query);
}
+ // Assign this packet to a class, if possible
+ classifyPacket(query);
+
try {
NameChangeRequestPtr ncr;
switch (query->getType()) {
@@ -610,10 +643,11 @@ Dhcpv6Srv::generateServerID() {
seconds -= DUID_TIME_EPOCH;
OptionBuffer srvid(8 + iface->getMacLen());
- writeUint16(DUID::DUID_LLT, &srvid[0]);
- writeUint16(HWTYPE_ETHERNET, &srvid[2]);
- writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
- memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen());
+ // We know that the buffer is more than 8 bytes long at this point.
+ writeUint16(DUID::DUID_LLT, &srvid[0], 2);
+ writeUint16(HWTYPE_ETHERNET, &srvid[2], 2);
+ writeUint32(static_cast<uint32_t>(seconds), &srvid[4], 4);
+ memcpy(&srvid[8], iface->getMac(), iface->getMacLen());
serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
srvid.begin(), srvid.end()));
@@ -626,8 +660,8 @@ Dhcpv6Srv::generateServerID() {
// See Section 9.3 of RFC3315 for details.
OptionBuffer srvid(12);
- writeUint16(DUID::DUID_EN, &srvid[0]);
- writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);
+ writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
+ writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
// Length of the identifier is company specific. I hereby declare
// ISC "standard" of 6 bytes long pseudo-random numbers.
@@ -887,8 +921,7 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
}
void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& fqdn) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
@@ -941,10 +974,9 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
+ OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
boost::dynamic_pointer_cast<
- Option6IA>(opt->second),
- fqdn);
+ Option6IA>(opt->second));
if (answer_opt) {
answer->addOption(answer_opt);
}
@@ -964,14 +996,14 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
}
}
-Option6ClientFqdnPtr
-Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
+void
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
// Get Client FQDN Option from the client's message. If this option hasn't
// been included, do nothing.
Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
if (!fqdn) {
- return (fqdn);
+ return;
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
@@ -1022,13 +1054,14 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
// generate one.
if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
std::ostringstream name;
- if (fqdn->getDomainName().empty()) {
- name << FQDN_GENERATED_PARTIAL_NAME;
+ if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
+ fqdn->setDomainName("", Option6ClientFqdn::PARTIAL);
+
} else {
name << fqdn->getDomainName();
+ name << "." << FQDN_PARTIAL_SUFFIX;
+ fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
}
- name << "." << FQDN_PARTIAL_SUFFIX;
- fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
// Server may be configured to replace a name supplied by a client,
// even if client supplied fully qualified domain-name.
@@ -1039,58 +1072,17 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
}
- // Return the FQDN option which can be included in the server's response.
- // Note that it doesn't have to be included, if client didn't request
- // it using ORO and server is not configured to always include it.
- return (fqdn_resp);
+ // The FQDN has been processed successfully. Let's append it to the
+ // response to be sent to a client. Note that the Client FQDN option is
+ // always sent back to the client if Client FQDN was included in the
+ // client's message.
+ answer->addOption(fqdn_resp);
}
-
void
-Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question,
- Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& fqdn) {
-
- // If FQDN is NULL, it means that client did not request DNS Update, plus
- // server doesn't force updates.
- if (!fqdn) {
- return;
- }
-
- // Server sends back the FQDN option to the client if client has requested
- // it using Option Request Option. However, server may be configured to
- // send the FQDN option in its response, regardless whether client requested
- // it or not.
- bool include_fqdn = FQDN_ALWAYS_INCLUDE;
- if (!include_fqdn) {
- OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
- OptionUint16Array>(question->getOption(D6O_ORO));
- if (oro) {
- const std::vector<uint16_t>& values = oro->getValues();
- for (int i = 0; i < values.size(); ++i) {
- if (values[i] == D6O_CLIENT_FQDN) {
- include_fqdn = true;
- }
- }
- }
- }
-
- if (include_fqdn) {
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
- DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText());
- answer->addOption(fqdn);
- }
-
-}
-
-void
-Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& opt_fqdn) {
-
- // It is likely that client haven't included the FQDN option in the message
- // and server is not configured to always update DNS. In such cases,
- // FQDN option will be NULL. This is valid state, so we simply return.
- if (!opt_fqdn) {
+Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
+ // Don't create NameChangeRequests if DNS updates are disabled.
+ if (!FQDN_ENABLE_UPDATE) {
return;
}
@@ -1103,6 +1095,14 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
<< " NULL when creating DNS NameChangeRequest");
}
+ // It is likely that client haven't included the FQDN option. In such case,
+ // FQDN option will be NULL. This is valid state, so we simply return.
+ Option6ClientFqdnPtr opt_fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+ if (!opt_fqdn) {
+ return;
+ }
+
// Get the Client Id. It is mandatory and a function creating a response
// would have thrown an exception if it was missing. Thus throwning
// Unexpected if it is missing as it is a programming error.
@@ -1130,8 +1130,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
for (OptionCollection::const_iterator answer_ia =
answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
- // @todo IA_NA may contain multiple addresses. We should process
- // each address individually. Currently we get only one.
+ /// @todo IA_NA may contain multiple addresses. We should process
+ /// each address individually. Currently we get only one.
Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR));
// We need an address to create a name-to-address mapping.
@@ -1157,31 +1157,33 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+
+ /// @todo Currently we create NCR with the first IPv6 address that
+ /// is carried in one of the IA_NAs. In the future, the NCR API should
+ /// be extended to map multiple IPv6 addresses to a single FQDN.
+ /// In such case, this return statement will be removed.
+ return;
}
}
void
Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
- // If we haven't performed a DNS Update when lease was acquired,
- // there is nothing to do here.
- if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
+ // Don't create NameChangeRequests if DNS updates are disabled.
+ if (!FQDN_ENABLE_UPDATE) {
return;
}
- // When lease was added into a database the host name should have
- // been added. The hostname can be empty if someone messed up in the
- // lease data base and removed the hostname.
- if (lease->hostname_.empty()) {
- LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME)
- .arg(lease->addr_.toText());
+ // If we haven't performed a DNS Update when lease was acquired,
+ // there is nothing to do here.
+ if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
return;
}
// If hostname is non-empty, try to convert it to wire format so as
// DHCID can be computed from it. This may throw an exception if hostname
- // has invalid format. Again, this should be only possible in case of
- // manual intervention in the database. Note that the last parameter
- // passed to the writeFqdn function forces conversion of the FQDN
+ // has invalid format or is empty. Again, this should be only possible
+ // in case of manual intervention in the database. Note that the last
+ // parameter passed to the writeFqdn function forces conversion of the FQDN
// to lower case. This is required by the RFC4701, section 3.5.
// The DHCID computation is further in this function.
std::vector<uint8_t> hostname_wire;
@@ -1189,7 +1191,8 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
} catch (const Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME)
- .arg(lease->hostname_);
+ .arg(lease->hostname_.empty() ? "(empty)" : lease->hostname_)
+ .arg(lease->addr_.toText());
return;
}
@@ -1199,7 +1202,7 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
if (!lease->duid_) {
isc_throw(isc::Unexpected, "DUID must be set when creating"
<< " NameChangeRequest for DNS records removal for "
- << lease->addr_.toText());
+ << lease->addr_);
}
isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
@@ -1230,14 +1233,14 @@ Dhcpv6Srv::sendNameChangeRequests() {
OptionPtr
Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
- const Option6ClientFqdnPtr& fqdn) {
+ const Pkt6Ptr& query, const Pkt6Ptr& answer,
+ boost::shared_ptr<Option6IA> ia) {
// If there is no subnet selected for handling this IA_NA, the only thing to do left is
// to say that we are sorry, but the user won't get an address. As a convenience, we
// use a different status text to indicate that (compare to the same status code,
// but different wording below)
if (!subnet) {
- // Create empty IA_NA option with IAID matching the request.
+ // Creatasse empty IA_NA option with IAID matching the request.
// Note that we don't use OptionDefinition class to create this option.
// This is because we prefer using a constructor of Option6IA that
// initializes IAID. Otherwise we would have to use setIAID() after
@@ -1252,16 +1255,16 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// Check if the client sent us a hint in his IA_NA. Clients may send an
// address in their IA_NA options as a suggestion (e.g. the last address
// they used before).
- boost::shared_ptr<Option6IAAddr> hintOpt = boost::dynamic_pointer_cast<Option6IAAddr>
- (ia->getOption(D6O_IAADDR));
+ boost::shared_ptr<Option6IAAddr> hint_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
IOAddress hint("::");
- if (hintOpt) {
- hint = hintOpt->getAddress();
+ if (hint_opt) {
+ hint = hint_opt->getAddress();
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_REQUEST)
- .arg(duid?duid->toText():"(no-duid)").arg(ia->getIAID())
- .arg(hintOpt?hint.toText():"(no hint)");
+ .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID())
+ .arg(hint_opt ? hint.toText() : "(no hint)");
// "Fake" allocation is processing of SOLICIT message. We pretend to do an
// allocation, but we do not put the lease in the database. That is ok,
@@ -1285,6 +1288,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// the update.
bool do_fwd = false;
bool do_rev = false;
+ Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
if (fqdn) {
// Flag S must not coexist with flag N being set to 1, so if S=1
// server takes responsibility for both reverse and forward updates.
@@ -1306,13 +1311,15 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
+ Lease6Collection old_leases;
Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
ia->getIAID(),
hint, Lease::TYPE_NA,
do_fwd, do_rev,
hostname,
fake_allocation,
- callout_handle);
+ callout_handle,
+ old_leases);
/// @todo: Handle more than one lease
Lease6Ptr lease;
if (!leases.empty()) {
@@ -1347,26 +1354,25 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// but this is considered waste of bandwidth as absence of status
// code is considered a success.
+ Lease6Ptr old_lease;
+ if (!old_leases.empty()) {
+ old_lease = *old_leases.begin();
+ }
// Allocation engine may have returned an existing lease. If so, we
// have to check that the FQDN settings we provided are the same
// that were set. If they aren't, we will have to remove existing
// DNS records and update the lease with the new settings.
- if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
- (lease->fqdn_rev_ != do_rev)) {
+ if (!fake_allocation && old_lease &&
+ !lease->hasIdenticalFqdn(*old_lease)) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
- .arg(lease->toText())
+ .arg(old_lease->toText())
.arg(hostname)
.arg(do_rev ? "true" : "false")
.arg(do_fwd ? "true" : "false");
// Schedule removal of the existing lease.
- createRemovalNameChangeRequest(lease);
- // Set the new lease properties and update.
- lease->hostname_ = hostname;
- lease->fqdn_fwd_ = do_fwd;
- lease->fqdn_rev_ = do_rev;
- LeaseMgrFactory::instance().updateLease6(lease);
+ createRemovalNameChangeRequest(old_lease);
}
} else {
@@ -1434,13 +1440,15 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
+ Lease6Collection old_leases;
Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
- ia->getIAID(),
- hint, Lease::TYPE_PD,
- false, false,
- string(),
- fake_allocation,
- callout_handle);
+ ia->getIAID(),
+ hint, Lease::TYPE_PD,
+ false, false,
+ string(),
+ fake_allocation,
+ callout_handle,
+ old_leases);
if (!leases.empty()) {
@@ -1488,8 +1496,8 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
OptionPtr
Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
- const Option6ClientFqdnPtr& fqdn) {
+ const Pkt6Ptr& query, const Pkt6Ptr& answer,
+ boost::shared_ptr<Option6IA> ia) {
if (!subnet) {
// There's no subnet select for this client. There's nothing to renew.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -1538,6 +1546,8 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// the update.
bool do_fwd = false;
bool do_rev = false;
+ Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
if (fqdn) {
if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
do_fwd = true;
@@ -1730,8 +1740,7 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
}
void
-Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
- const Option6ClientFqdnPtr& fqdn) {
+Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
@@ -1775,10 +1784,9 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
+ OptionPtr answer_opt = renewIA_NA(subnet, duid, renew, reply,
boost::dynamic_pointer_cast<
- Option6IA>(opt->second),
- fqdn);
+ Option6IA>(opt->second));
if (answer_opt) {
reply->addOption(answer_opt);
}
@@ -2175,13 +2183,14 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
appendRequestedOptions(solicit, advertise);
appendRequestedVendorOptions(solicit, advertise);
- Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
- assignLeases(solicit, advertise, fqdn);
- appendClientFqdn(solicit, advertise, fqdn);
+ processClientFqdn(solicit, advertise);
+ assignLeases(solicit, advertise);
// Note, that we don't create NameChangeRequests here because we don't
// perform DNS Updates for Solicit. Client must send Request to update
// DNS.
+ generateFqdn(advertise);
+
return (advertise);
}
@@ -2197,10 +2206,10 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
appendRequestedOptions(request, reply);
appendRequestedVendorOptions(request, reply);
- Option6ClientFqdnPtr fqdn = processClientFqdn(request);
- assignLeases(request, reply, fqdn);
- appendClientFqdn(request, reply, fqdn);
- createNameChangeRequests(reply, fqdn);
+ processClientFqdn(request, reply);
+ assignLeases(request, reply);
+ generateFqdn(reply);
+ createNameChangeRequests(reply);
return (reply);
}
@@ -2216,12 +2225,12 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
appendDefaultOptions(renew, reply);
appendRequestedOptions(renew, reply);
- Option6ClientFqdnPtr fqdn = processClientFqdn(renew);
- renewLeases(renew, reply, fqdn);
- appendClientFqdn(renew, reply, fqdn);
- createNameChangeRequests(reply, fqdn);
+ processClientFqdn(renew, reply);
+ renewLeases(renew, reply);
+ generateFqdn(reply);
+ createNameChangeRequests(reply);
- return reply;
+ return (reply);
}
Pkt6Ptr
@@ -2352,10 +2361,13 @@ Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
// The buffer being read comprises a set of options, each starting with
// a two-byte type code and a two-byte length field.
while (offset + 4 <= length) {
- uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+ // At this point, from the while condition, we know that there
+ // are at least 4 bytes available following offset in the
+ // buffer.
+ uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
offset += 2;
- uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+ uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
offset += 2;
if (offset + opt_len > length) {
@@ -2423,5 +2435,112 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(errmsg);
}
+void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
+
+ boost::shared_ptr<OptionCustom> vclass =
+ boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS));
+
+ if (!vclass) {
+ return;
+ }
+
+ string classes = "";
+
+ // DOCSIS specific section
+ if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+ .find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+ pkt->addClass(DOCSIS3_CLASS_MODEM);
+ classes += string(DOCSIS3_CLASS_MODEM) + " ";
+ } else
+ if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+ .find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+ pkt->addClass(DOCSIS3_CLASS_EROUTER);
+ classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+ }else
+ {
+ // Otherwise use the string as is
+ classes += vclass->readString(VENDOR_CLASS_STRING_INDEX);
+ pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX));
+ }
+
+ if (!classes.empty()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
+ .arg(classes);
+ }
+}
+
+void
+Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
+ if (!answer) {
+ isc_throw(isc::Unexpected, "an instance of the object encapsulating"
+ " a message must not be NULL when generating FQDN");
+ }
+
+ // It is likely that client haven't included the FQDN option. In such case,
+ // FQDN option will be NULL. Also, there is nothing to do if the option
+ // is present and conveys the non-empty FQDN.
+ Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+ if (!fqdn || !fqdn->getDomainName().empty()) {
+ return;
+ }
+
+ // Get the first IA_NA acquired for the client.
+ OptionPtr ia = answer->getOption(D6O_IA_NA);
+ if (!ia) {
+ return;
+ }
+
+ // If it has any IAAddr, use the first one to generate unique FQDN.
+ Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+ Option6IAAddr>(ia->getOption(D6O_IAADDR));
+ if (!iaaddr) {
+ return;
+ }
+ // Get the IPv6 address acquired by the client.
+ IOAddress addr = iaaddr->getAddress();
+ std::string hostname = addr.toText();
+ // Colons may not be ok for FQDNs so let's replace them with hyphens.
+ std::replace(hostname.begin(), hostname.end(), ':', '-');
+ std::ostringstream stream;
+ // The final FQDN consists of the partial domain name and the suffix.
+ // For example, if the acquired address is 2001:db8:1::2, the generated
+ // FQDN may be:
+ // host-2001-db8:1--2.example.com.
+ // where prefix 'host' should be configurable. The domain name suffix
+ // should also be configurable.
+ stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
+ try {
+ // The lease has been acquired but the FQDN for this lease hasn't
+ // been updated in the lease database. We now have new FQDN
+ // generated, so the lease database has to be updated here.
+ // However, never update lease database for Advertise, just send
+ // our notion of client's FQDN in the Client FQDN option.
+ if (answer->getType() != DHCPV6_ADVERTISE) {
+ Lease6Ptr lease =
+ LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ if (lease) {
+ lease->hostname_ = stream.str();
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ } else {
+ isc_throw(isc::Unexpected, "there is no lease in the database "
+ " for address " << addr << ", so as it is impossible"
+ " to update FQDN data. This is a programmatic error"
+ " as the given address is now being handed to the"
+ " client");
+ }
+ }
+
+ // Set the generated FQDN in the Client FQDN option.
+ fqdn->setDomainName(stream.str(), Option6ClientFqdn::FULL);
+
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcp6_logger, DHCP6_NAME_GEN_UPDATE_FAIL)
+ .arg(hostname)
+ .arg(ex.what());
+ }
+}
+
};
};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 26a7d7189e..3529841abb 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -119,6 +119,16 @@ public:
protected:
+ /// @brief Compare received server id with our server id
+ ///
+ /// Checks if the server id carried in a query from a client matches
+ /// server identifier being used by the server.
+ ///
+ /// @param pkt DHCPv6 packet carrying server identifier to be checked.
+ /// @return true if server id carried in the query matches server id
+ /// used by the server; false otherwise.
+ bool testServerID(const Pkt6Ptr& pkt);
+
/// @brief verifies if specified packet meets RFC requirements
///
/// Checks if mandatory option is really there, that forbidden option
@@ -214,15 +224,17 @@ protected:
/// @param subnet subnet the client is connected to
/// @param duid client's duid
/// @param query client's message (typically SOLICIT or REQUEST)
+ /// @param answer server's response to the client's message. This
+ /// message should contain Client FQDN option being sent by the server
+ /// to the client (if the client sent this option to the server).
/// @param ia pointer to client's IA_NA option (client's request)
- /// @param fqdn A DHCPv6 Client FQDN %Option generated in a response to the
- /// FQDN option sent by a client.
+ ///
/// @return IA_NA option (server's response)
OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
const isc::dhcp::DuidPtr& duid,
const isc::dhcp::Pkt6Ptr& query,
- Option6IAPtr ia,
- const Option6ClientFqdnPtr& fqdn);
+ const isc::dhcp::Pkt6Ptr& answer,
+ Option6IAPtr ia);
/// @brief Processes IA_PD option (and assigns prefixes if necessary).
///
@@ -250,12 +262,14 @@ protected:
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
/// @param query client's message
+ /// @param answer server's response to the client's message. This
+ /// message should contain Client FQDN option being sent by the server
+ /// to the client (if the client sent this option to the server).
/// @param ia IA_NA option that is being renewed
- /// @param fqdn DHCPv6 Client FQDN Option included in the server's response
/// @return IA_NA option (server's response)
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
- const Option6ClientFqdnPtr& fqdn);
+ const Pkt6Ptr& query, const Pkt6Ptr& answer,
+ boost::shared_ptr<Option6IA> ia);
/// @brief Renews specific IA_PD option
///
@@ -352,11 +366,10 @@ protected:
/// @todo: Extend this method once TA and PD becomes supported
///
/// @param question client's message (with requested IA_NA)
- /// @param answer server's message (IA_NA options will be added here)
- /// @param fqdn an FQDN option generated in a response to the client's
- /// FQDN option.
- void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& fqdn);
+ /// @param answer server's message (IA_NA options will be added here).
+ /// This message should contain Client FQDN option being sent by the server
+ /// to the client (if the client sent this option to the server).
+ void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
/// @brief Processes Client FQDN Option.
///
@@ -365,7 +378,7 @@ protected:
/// Received option comprises flags field which controls what DNS updates
/// server should do. Server may override client's preference based on
/// the current configuration. Server indicates that it has overridden
- /// the preference by storing DHCPv6 Client Fqdn %Option with the
+ /// the preference by storing DHCPv6 Client FQDN option with the
/// appropriate flags in the response to a client. This option is also
/// used to communicate the client's domain-name which should be sent
/// to the DNS in the update. Again, server may act upon the received
@@ -376,25 +389,10 @@ protected:
/// held in this function.
///
/// @param question Client's message.
- ///
- /// @return FQDN option produced in the response to the client's message.
- Option6ClientFqdnPtr processClientFqdn(const Pkt6Ptr& question);
-
- /// @brief Adds DHCPv6 Client FQDN %Option to the server response.
- ///
- /// This function will add the specified FQDN option into the server's
- /// response when FQDN is not NULL and server is either configured to
- /// always include the FQDN in the response or client requested it using
- /// %Option Request %Option.
- /// This function is exception safe.
- ///
- /// @param question A message received from the client.
- /// @param [out] answer A server's response where FQDN option will be added.
- /// @param fqdn A DHCPv6 Client FQDN %Option to be added to the server's
- /// response to a client.
- void appendClientFqdn(const Pkt6Ptr& question,
- Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& fqdn);
+ /// @param answer Server's response to a client. If server generated
+ /// Client FQDN option for the client, this option is stored in this
+ /// object.
+ void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer);
/// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
/// based on the DHCPv6 Client FQDN %Option.
@@ -410,11 +408,9 @@ protected:
///
/// @todo Add support for multiple IAADDR options in the IA_NA.
///
- /// @param answer A message beging sent to the Client.
- /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
- /// response message sent to a client.
- void createNameChangeRequests(const Pkt6Ptr& answer,
- const Option6ClientFqdnPtr& fqdn_answer);
+ /// @param answer A message beging sent to the Client. If it holds the
+ /// Client FQDN option, this option is used to create NameChangeRequests.
+ void createNameChangeRequests(const Pkt6Ptr& answer);
/// @brief Creates a @c isc::dhcp_ddns::NameChangeRequest which requests
/// removal of DNS entries for a particular lease.
@@ -450,10 +446,7 @@ protected:
/// as IA_NA/IAADDR to reply packet.
/// @param renew client's message asking for renew
/// @param reply server's response
- /// @param fqdn A DHCPv6 Client FQDN %Option generated in the response to the
- /// client's FQDN option.
- void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
- const Option6ClientFqdnPtr& fqdn);
+ void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
/// @brief Attempts to release received addresses
///
@@ -533,6 +526,16 @@ protected:
size_t* relay_msg_offset,
size_t* relay_msg_len);
+ /// @brief Assigns incoming packet to zero or more classes.
+ ///
+ /// @note For now, the client classification is very simple. It just uses
+ /// content of the vendor-class-identifier option as a class. The resulting
+ /// class will be stored in packet (see @ref isc::dhcp::Pkt6::classes_ and
+ /// @ref isc::dhcp::Pkt6::inClass).
+ ///
+ /// @param pkt packet to be classified
+ void classifyPacket(const Pkt6Ptr& pkt);
+
private:
/// @brief Implements the error handler for socket open failure.
@@ -544,6 +547,48 @@ private:
/// @param errmsg An error message containing a cause of the failure.
static void ifaceMgrSocket6ErrorHandler(const std::string& errmsg);
+ /// @brief Generate FQDN to be sent to a client if none exists.
+ ///
+ /// This function is meant to be called by the functions which process
+ /// client's messages. The function should be called after a function
+ /// which creates FQDN option for the client. This option must exist
+ /// in the answer message specified as an argument. It must also be
+ /// called after functions which assign leases for a client. The
+ /// IA options being a result of lease acquisition must be appended
+ /// to the message specified as a parameter.
+ ///
+ /// If the Client FQDN option being present in the message carries empty
+ /// hostname, this function will attempt to generate hostname from the
+ /// IPv6 address being acquired by the client. The IPv6 address is retrieved
+ /// from the IA_NA option carried in the specified message. If multiple
+ /// addresses are present in the particular IA_NA option or multiple IA_NA
+ /// options exist, the first address found is selected.
+ ///
+ /// The IPv6 address is converted to the hostname using the following
+ /// pattern:
+ /// @code
+ /// prefix-converted-ip-address.domain-name-suffix.
+ /// @endcode
+ /// where:
+ /// - prefix is a configurable prefix string appended to all auto-generated
+ /// hostnames.
+ /// - converted-ip-address is created by replacing all colons from the IPv6
+ /// address with hyphens.
+ /// - domain-name-suffix is a suffix for a domain name that, together with
+ /// the other parts, constitute the fully qualified domain name.
+ ///
+ /// When hostname is successfully generated, it is either used to update
+ /// FQDN-related fields in a lease database or to update the Client FQDN
+ /// option being sent back to the client. The lease database update is
+ /// NOT performed if Advertise message is being processed.
+ ///
+ /// @param answer Message being sent to a client, which may hold IA_NA
+ /// and Client FQDN options to be used to generate name for a client.
+ ///
+ /// @throw isc::Unexpected if specified message is NULL. This is treated
+ /// as a programmatic error.
+ void generateFqdn(const Pkt6Ptr& answer);
+
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
/// It must be a pointer, because we will support changing engines
diff --git a/src/bin/dhcp6/tests/.gitignore b/src/bin/dhcp6/tests/.gitignore
index e170d18915..f4ca7830e1 100644
--- a/src/bin/dhcp6/tests/.gitignore
+++ b/src/bin/dhcp6/tests/.gitignore
@@ -1 +1,4 @@
/dhcp6_unittests
+/marker_file.h
+/test_data_files_config.h
+/test_libraries.h
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index f548ec2105..92a18cf27f 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -54,7 +54,8 @@ if HAVE_GTEST
# to unexpected errors. For this reason, the --enable-static-link option is
# ignored for unit tests built here.
-lib_LTLIBRARIES = libco1.la libco2.la
+nodistdir=$(abs_top_builddir)/src/bin/dhcp6/tests
+nodist_LTLIBRARIES = libco1.la libco2.la
libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 242017236f..2bc5259bcb 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -138,19 +138,19 @@ public:
params["name"] = param_value;
params["space"] = "dhcp6";
params["code"] = "38";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "space") {
params["name"] = "subscriber-id";
params["space"] = param_value;
params["code"] = "38";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "code") {
params["name"] = "subscriber-id";
params["space"] = "dhcp6";
params["code"] = param_value;
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = "False";
} else if (parameter == "data") {
params["name"] = "subscriber-id";
@@ -162,12 +162,20 @@ public:
params["name"] = "subscriber-id";
params["space"] = "dhcp6";
params["code"] = "38";
- params["data"] = "AB CDEF0105";
+ params["data"] = "ABCDEF0105";
params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
+ /// @brief Create simple configuration with single option.
+ ///
+ /// This function creates a configuration for a single option with
+ /// custom values for all parameters that describe the option.
+ ///
+ /// @params params map holding parameters and their values.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
std::string createConfigWithOption(const std::map<std::string,
std::string>& params)
{
@@ -176,6 +184,15 @@ public:
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
+ "\"option-def\": [ {"
+ " \"name\": \"bool-option\","
+ " \"code\": 1000,"
+ " \"type\": \"boolean\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\","
+ " \"encapsulate\": \"\""
+ "} ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
@@ -208,6 +225,62 @@ public:
return (stream.str());
}
+ /// @brief Returns an option from the subnet.
+ ///
+ /// This function returns an option from a subnet to which the
+ /// specified subnet address belongs. The option is identified
+ /// by its code.
+ ///
+ /// @param subnet_address Address which belongs to the subnet from
+ /// which the option is to be returned.
+ /// @param option_code Code of the option to be returned.
+ /// @param expected_options_count Expected number of options in
+ /// the particular subnet.
+ ///
+ /// @return Descriptor of the option. If the descriptor holds a
+ /// NULL option pointer, it means that there was no such option
+ /// in the subnet.
+ Subnet::OptionDescriptor
+ getOptionFromSubnet(const IOAddress& subnet_address,
+ const uint16_t option_code,
+ const uint16_t expected_options_count = 1) {
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address);
+ if (!subnet) {
+ /// @todo replace toText() with the use of operator <<.
+ ADD_FAILURE() << "A subnet for the specified address "
+ << subnet_address.toText()
+ << "does not exist in Config Manager";
+ }
+ Subnet::OptionContainerPtr options =
+ subnet->getOptionDescriptors("dhcp6");
+ if (expected_options_count != options->size()) {
+ ADD_FAILURE() << "The number of options in the subnet '"
+ << subnet_address.toText() << "' is different "
+ " than expected number of options '"
+ << expected_options_count << "'";
+ }
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 1) {
+ ADD_FAILURE() << "There is more than one option having the"
+ " option code '" << option_code << "' in a subnet '"
+ << subnet_address.toText() << "'. Expected "
+ " at most one option";
+ } else if (std::distance(range.first, range.second) == 0) {
+ return (Subnet::OptionDescriptor(OptionPtr(), false));
+ }
+
+ return (*range.first);
+ }
+
/// @brief Parse and Execute configuration
///
/// Parses a configuration and executes a configuration of the server.
@@ -305,6 +378,24 @@ public:
ASSERT_EQ(1, rcode_);
}
+ /// @brief Test invalid option paramater value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param params Map of parameters defining an option.
+ void
+ testInvalidOptionParam(const std::map<std::string, std::string>& params) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+ }
+
/// @brief Test option against given code and data.
///
/// @param option_desc option descriptor that carries the option to
@@ -346,6 +437,39 @@ public:
expected_data_len));
}
+ /// @brief Test option configuration.
+ ///
+ /// This function creates a configuration for a specified option using
+ /// a map of parameters specified as the argument. The map holds
+ /// name/value pairs which identifies option's configuration parameters:
+ /// - name
+ /// - space
+ /// - code
+ /// - data
+ /// - csv-format.
+ /// This function applies a new server configuration and checks that the
+ /// option being configured is inserted into CfgMgr. The raw contents of
+ /// this option are compared with the binary data specified as expected
+ /// data passed to this function.
+ ///
+ /// @param params Map of parameters defining an option.
+ /// @param option_code Option code.
+ /// @param expected_data Array containing binary data expected to be stored
+ /// in the configured option.
+ /// @param expected_data_len Length of the array holding reference data.
+ void testConfiguration(const std::map<std::string, std::string>& params,
+ const uint16_t option_code,
+ const uint8_t* expected_data,
+ const size_t expected_data_len) {
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+ // The subnet should now hold one option with the specified code.
+ Subnet::OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
+ ASSERT_TRUE(desc.option);
+ testOption(desc, option_code, expected_data, expected_data_len);
+ }
+
int rcode_; ///< Return code (see @ref isc::config::parseAnswer)
Dhcpv6Srv srv_; ///< Instance of the Dhcp6Srv used during tests
ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
@@ -436,8 +560,190 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
EXPECT_EQ(2000, subnet->getT2());
EXPECT_EQ(3000, subnet->getPreferred());
EXPECT_EQ(4000, subnet->getValid());
+
+ // Check that subnet-id is 1
+ EXPECT_EQ(1, subnet->getID());
+}
+
+// Goal of this test is to verify that multiple subnets get unique
+// subnet-ids. Also, test checks that it's possible to do reconfiguration
+// multiple times.
+TEST_F(Dhcp6ParserTest, multipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:2::/80\" ],"
+ " \"subnet\": \"2001:db8:2::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:3::/80\" ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:4::/80\" ],"
+ " \"subnet\": \"2001:db8:4::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ int cnt = 0; // Number of reconfigurations
+
+ do {
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Check subnet-ids of each subnet (it should be monotonously increasing)
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ EXPECT_EQ(2, subnets->at(1)->getID());
+ EXPECT_EQ(3, subnets->at(2)->getID());
+ EXPECT_EQ(4, subnets->at(3)->getID());
+
+ // Repeat reconfiguration process 10 times and check that the subnet-id
+ // is set to the same value. Technically, just two iterations would be
+ // sufficient, but it's nice to have a test that exercises reconfiguration
+ // a bit.
+ } while (++cnt < 10);
+}
+
+// Goal of this test is to verify that a previously configured subnet can be
+// deleted in subsequent reconfiguration.
+TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
+ ConstElementPtr x;
+
+ // All four subnets
+ string config4 = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:2::/80\" ],"
+ " \"subnet\": \"2001:db8:2::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:3::/80\" ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:4::/80\" ],"
+ " \"subnet\": \"2001:db8:4::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Three subnets (the last one removed)
+ string config_first3 = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:2::/80\" ],"
+ " \"subnet\": \"2001:db8:2::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:3::/80\" ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // Second subnet removed
+ string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:3::/80\" ],"
+ " \"subnet\": \"2001:db8:3::/64\" "
+ " },"
+ " {"
+ " \"pool\": [ \"2001:db8:4::/80\" ],"
+ " \"subnet\": \"2001:db8:4::/64\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // CASE 1: Configure 4 subnets, then reconfigure and remove the
+ // last one.
+
+ ElementPtr json = Element::fromJSON(config4);
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+ // Do the reconfiguration (the last subnet is removed)
+ json = Element::fromJSON(config_first3);
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ subnets = CfgMgr::instance().getSubnets6();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ EXPECT_EQ(2, subnets->at(1)->getID());
+ EXPECT_EQ(3, subnets->at(2)->getID());
+
+ /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+ /// from in between (not first, not last)
+
+#if 0
+ /// @todo: Uncomment subnet removal test as part of #3281.
+ json = Element::fromJSON(config4);
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ // Do reconfiguration
+ json = Element::fromJSON(config_second_removed);
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ subnets = CfgMgr::instance().getSubnets6();
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+ EXPECT_EQ(1, subnets->at(0)->getID());
+ // The second subnet (with subnet-id = 2) is no longer there
+ EXPECT_EQ(3, subnets->at(1)->getID());
+ EXPECT_EQ(4, subnets->at(2)->getID());
+#endif
}
+
+
// This test checks if it is possible to override global values
// on a per subnet basis.
TEST_F(Dhcp6ParserTest, subnetLocal) {
@@ -783,8 +1089,7 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
// verify that it was interpreted correctly by checking the last address
// value.
isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
- EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64).toText(),
- p6->getLastAddress().toText());
+ EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
}
// Goal of this test is verify that a list of PD pools can be configured.
@@ -917,8 +1222,7 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
// verify that it was interpreted correctly by checking the last address
// value.
isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
- EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64).toText(),
- p6->getLastAddress().toText());
+ EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
}
@@ -1519,7 +1823,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
" \"name\": \"subscriber-id\","
" \"space\": \"dhcp6\","
" \"code\": 38,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -1600,7 +1904,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
" \"name\": \"subscriber-id\","
" \"space\": \"dhcp6\","
" \"code\": 38,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
@@ -1892,6 +2196,89 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
sizeof(user_class_expected));
}
+// The goal of this test is to check that the option carrying a boolean
+// value can be configured using one of the values: "true", "false", "0"
+// or "1".
+TEST_F(Dhcp6ParserTest, optionDataBoolean) {
+ // Create configuration. Use standard option 1000.
+ std::map<std::string, std::string> params;
+ params["name"] = "bool-option";
+ params["space"] = "dhcp6";
+ params["code"] = "1000";
+ params["data"] = "true";
+ params["csv-format"] = "true";
+
+ std::string config = createConfigWithOption(params);
+ ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
+ " boolean value"));
+
+ // The subnet should now hold one option with the code 1000.
+ Subnet::OptionDescriptor desc =
+ getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000);
+ ASSERT_TRUE(desc.option);
+
+ // This option should be set to "true", represented as 0x1 in the option
+ // buffer.
+ uint8_t expected_option_data[] = {
+ 0x1
+ };
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "1" value. This should have the same
+ // effect as if "true" was specified.
+ params["data"] = "1";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The value of "1" with a few leading zeros should work too.
+ params["data"] = "00001";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Configure the option with the "false" value.
+ params["data"] = "false";
+ // The option buffer should now hold the value of 0.
+ expected_option_data[0] = 0;
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Specifying "0" should have the same effect as "false".
+ params["data"] = "0";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The same effect should be for multiple 0 chars.
+ params["data"] = "00000";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // Bogus values should not be accepted.
+ params["data"] = "bugus";
+ testInvalidOptionParam(params);
+
+ params["data"] = "2";
+ testInvalidOptionParam(params);
+
+ // Now let's test that it is possible to use binary format.
+ params["data"] = "0";
+ params["csv-format"] = "false";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // The binary 1 should work as well.
+ params["data"] = "1";
+ expected_option_data[0] = 1;
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+ // As well as an even number of digits.
+ params["data"] = "01";
+ testConfiguration(params, 1000, expected_option_data,
+ sizeof(expected_option_data));
+
+}
+
// Verify that empty option name is rejected in the configuration.
TEST_F(Dhcp6ParserTest, optionNameEmpty) {
// Empty option names not allowed.
@@ -1949,14 +2336,6 @@ TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
testInvalidOptionParam("0x0102", "data");
}
-// Verify that option data consisting od an odd number of
-// hexadecimal digits is rejected in the configuration.
-TEST_F(Dhcp6ParserTest, optionDataOddLength) {
- // Option code 0 is reserved and should not be accepted
- // by configuration parser.
- testInvalidOptionParam("123", "data");
-}
-
// Verify that either lower or upper case characters are allowed
// to specify the option data.
TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
@@ -2063,7 +2442,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
" \"name\": \"option-one\","
" \"space\": \"vendor-4491\","
" \"code\": 100,"
- " \"data\": \"AB CDEF0105\","
+ " \"data\": \"ABCDEF0105\","
" \"csv-format\": False"
" },"
" {"
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 393d466ce3..b26a7a301c 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -642,12 +642,12 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
checkClientId(reply3, clientid3);
// Finally check that the addresses offered are different
- EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
- EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
- EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
- cout << "Offered address to client1=" << addr1->getAddress().toText() << endl;
- cout << "Offered address to client2=" << addr2->getAddress().toText() << endl;
- cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
+ EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+ EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+ EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+ cout << "Offered address to client1=" << addr1->getAddress() << endl;
+ cout << "Offered address to client2=" << addr2->getAddress() << endl;
+ cout << "Offered address to client3=" << addr3->getAddress() << endl;
}
// This test verifies that incoming REQUEST can be handled properly, that a
@@ -852,12 +852,12 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
checkClientId(reply3, clientid3);
// Finally check that the addresses offered are different
- EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
- EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
- EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
- cout << "Assigned address to client1=" << addr1->getAddress().toText() << endl;
- cout << "Assigned address to client2=" << addr2->getAddress().toText() << endl;
- cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
+ EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+ EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+ EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+ cout << "Assigned address to client1=" << addr1->getAddress() << endl;
+ cout << "Assigned address to client2=" << addr2->getAddress() << endl;
+ cout << "Assigned address to client3=" << addr3->getAddress() << endl;
}
// This test verifies that incoming (positive) RENEW can be handled properly, that a
@@ -1073,6 +1073,42 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
}
+// Check that the server is testing if server identifier received in the
+// query, matches server identifier used by the server.
+TEST_F(Dhcpv6SrvTest, testServerID) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ std::vector<uint8_t> bin;
+
+ // diud_llt constructed with: time = 0, macaddress = 00:00:00:00:00:00
+ // it's necessary to generate server identifier option
+ isc::util::encode::decodeHex("0001000100000000000000000000", bin);
+ // Now create server identifier option
+ OptionPtr serverid = OptionPtr(new Option(Option::V6, D6O_SERVERID, bin));
+
+ // Server identifier option is MANDATORY in Request message.
+ // Add server identifier option with different value from one that
+ // server is using.
+ req->addOption(serverid);
+
+ // Message shoud be dropped
+ EXPECT_FALSE(srv.testServerID(req));
+
+ // Delete server identifier option and add new one, with same value as
+ // server's server identifier.
+ req->delOption(D6O_SERVERID);
+ req->addOption(srv.getServerID());
+
+ // With proper server identifier we expect true
+ EXPECT_TRUE(srv.testServerID(req));
+
+ // server-id MUST NOT appear in Solicit, so check if server is
+ // not dropping a message without server id.
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ EXPECT_TRUE(srv.testServerID(req));
+}
// This test verifies if selectSubnet() selects proper subnet for a given
// source address.
@@ -1673,6 +1709,35 @@ TEST_F(Dhcpv6SrvTest, unpackOptions) {
EXPECT_EQ(0x0, option_bar->getValue());
}
+// Checks if client packets are classified properly
+TEST_F(Dhcpv6SrvTest, clientClassification) {
+
+ NakedDhcpv6Srv srv(0);
+
+ // Let's create a relayed SOLICIT. This particular relayed SOLICIT has
+ // vendor-class set to docsis3.0
+ Pkt6Ptr sol1;
+ ASSERT_NO_THROW(sol1 = captureDocsisRelayedSolicit());
+ ASSERT_NO_THROW(sol1->unpack());
+
+ srv.classifyPacket(sol1);
+
+ // It should belong to docsis3.0 class. It should not belong to eRouter1.0
+ EXPECT_TRUE(sol1->inClass("docsis3.0"));
+ EXPECT_FALSE(sol1->inClass("eRouter1.0"));
+
+ // Let's get a relayed SOLICIT. This particular relayed SOLICIT has
+ // vendor-class set to eRouter1.0
+ Pkt6Ptr sol2;
+ ASSERT_NO_THROW(sol2 = captureeRouterRelayedSolicit());
+ ASSERT_NO_THROW(sol2->unpack());
+
+ srv.classifyPacket(sol2);
+
+ EXPECT_TRUE(sol2->inClass("eRouter1.0"));
+ EXPECT_FALSE(sol2->inClass("docsis3.0"));
+}
+
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
index 93b6e44605..88dee363b4 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.cc
@@ -80,12 +80,12 @@ Dhcpv6SrvTest::checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
addr->getAddress());
if (!lease) {
- std::cout << "Lease for " << addr->getAddress().toText()
+ std::cout << "Lease for " << addr->getAddress()
<< " not found in the database backend.";
return (Lease6Ptr());
}
- EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+ EXPECT_EQ(addr->getAddress(), lease->addr_);
EXPECT_TRUE(*lease->duid_ == *duid);
EXPECT_EQ(ia->getIAID(), lease->iaid_);
EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
@@ -101,12 +101,12 @@ Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
prefix->getAddress());
if (!lease) {
- std::cout << "PD lease for " << prefix->getAddress().toText()
+ std::cout << "PD lease for " << prefix->getAddress()
<< " not found in the database backend.";
return (Lease6Ptr());
}
- EXPECT_EQ(prefix->getAddress().toText(), lease->addr_.toText());
+ EXPECT_EQ(prefix->getAddress(), lease->addr_);
EXPECT_TRUE(*lease->duid_ == *duid);
EXPECT_EQ(ia->getIAID(), lease->iaid_);
EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
index 2b6241ec08..17cd9e8cc7 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.h
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -111,7 +111,9 @@ public:
using Dhcpv6Srv::createRemovalNameChangeRequest;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::testServerID;
using Dhcpv6Srv::sanityCheck;
+ using Dhcpv6Srv::classifyPacket;
using Dhcpv6Srv::loadServerID;
using Dhcpv6Srv::writeServerID;
using Dhcpv6Srv::unpackOptions;
@@ -477,6 +479,7 @@ public:
Pkt6Ptr captureSimpleSolicit();
Pkt6Ptr captureRelayedSolicit();
Pkt6Ptr captureDocsisRelayedSolicit();
+ Pkt6Ptr captureeRouterRelayedSolicit();
/// @brief Auxiliary method that sets Pkt6 fields
///
diff --git a/src/bin/dhcp6/tests/fqdn_unittest.cc b/src/bin/dhcp6/tests/fqdn_unittest.cc
index 8a2da6904c..952420d953 100644
--- a/src/bin/dhcp6/tests/fqdn_unittest.cc
+++ b/src/bin/dhcp6/tests/fqdn_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -227,9 +227,6 @@ public:
/// This function verifies that the FQDN option returned is correct.
///
/// @param msg_type A type of the client's message.
- /// @param use_oro A boolean value which indicates whether the DHCPv6 ORO
- /// option (requesting return of the FQDN option by the server) should be
- /// included in the client's message (if true), or not included (if false).
/// @param in_flags A value of flags field to be set for the FQDN carried
/// in the client's message.
/// @param in_domain_name A domain name to be carried in the client's FQDN
@@ -240,7 +237,6 @@ public:
/// @param exp_domain_name A domain name expected in the FQDN sent by a
/// server.
void testFqdn(const uint16_t msg_type,
- const bool use_oro,
const uint8_t in_flags,
const std::string& in_domain_name,
const Option6ClientFqdn::DomainNameType in_domain_type,
@@ -251,11 +247,14 @@ public:
in_flags,
in_domain_name,
in_domain_type,
- use_oro);
+ true);
ASSERT_TRUE(getClientFqdnOption(question));
- Option6ClientFqdnPtr answ_fqdn;
- ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
+ Pkt6Ptr answer(new Pkt6(msg_type == DHCPV6_SOLICIT ? DHCPV6_ADVERTISE :
+ DHCPV6_REPLY, question->getTransid()));
+ ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
+ Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
ASSERT_TRUE(answ_fqdn);
const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
@@ -267,7 +266,19 @@ public:
EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
- EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+ // If server is configured to generate full FQDN for a client, and/or
+ // client sent empty FQDN the expected result of the processing by
+ // processClientFqdn is an empty, partial FQDN. This is an indication
+ // for the code which performs lease allocation that the FQDN has to
+ // be generated from the lease address.
+ if (exp_domain_name.empty()) {
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL,
+ answ_fqdn->getDomainNameType());
+
+ } else {
+ EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+
+ }
}
/// @brief Tests that the client's message holding an FQDN is processed
@@ -283,15 +294,19 @@ public:
/// that the server doesn't respond with the FQDN.
void testProcessMessage(const uint8_t msg_type,
const std::string& hostname,
+ const std::string& exp_hostname,
NakedDhcpv6Srv& srv,
const bool include_oro = true) {
// Create a message of a specified type, add server id and
// FQDN option.
OptionPtr srvid = srv.getServerID();
+ // Set the appropriate FQDN type. It must be partial if hostname is
+ // empty.
+ Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ?
+ Option6ClientFqdn::PARTIAL : Option6ClientFqdn::FULL);
+
Pkt6Ptr req = generateMessage(msg_type, Option6ClientFqdn::FLAG_S,
- hostname,
- Option6ClientFqdn::FULL,
- include_oro, srvid);
+ hostname, fqdn_type, include_oro, srvid);
// For different client's message types we have to invoke different
// functions to generate response.
@@ -337,13 +352,15 @@ public:
Lease6Ptr lease =
checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
ASSERT_TRUE(lease);
+ EXPECT_EQ(exp_hostname, lease->hostname_);
}
- if (include_oro) {
- ASSERT_TRUE(reply->getOption(D6O_CLIENT_FQDN));
- } else {
- ASSERT_FALSE(reply->getOption(D6O_CLIENT_FQDN));
- }
+ // The Client FQDN option should be always present in the server's
+ // response, regardless if requested using ORO or not.
+ Option6ClientFqdnPtr fqdn;
+ ASSERT_TRUE(fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(reply->getOption(D6O_CLIENT_FQDN)));
+ EXPECT_EQ(exp_hostname, fqdn->getDomainName());
}
/// @brief Verify that NameChangeRequest holds valid values.
@@ -394,7 +411,7 @@ public:
// Test server's response when client requests that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
- testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
@@ -403,7 +420,7 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
// Test server's response when client provides partial domain-name and requests
// that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
- testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost",
Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
@@ -411,14 +428,13 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
// Test server's response when client provides empty domain-name and requests
// that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
- testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "",
- Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
- "myhost.example.com.");
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "");
}
// Test server's response when client requests no DNS update.
TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
- testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
+ testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N,
"myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
"myhost.example.com.");
@@ -427,7 +443,7 @@ TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
// Test server's response when client requests that server delegates the AAAA
// update to the client and this delegation is not allowed.
TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
- testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
+ testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.",
Option6ClientFqdn::FULL,
Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
"myhost.example.com.");
@@ -439,10 +455,8 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
NakedDhcpv6Srv srv(0);
Pkt6Ptr answer;
- Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
- "myhost.example.com",
- Option6ClientFqdn::FULL);
- EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+
+ EXPECT_THROW(srv.createNameChangeRequests(answer),
isc::Unexpected);
}
@@ -456,22 +470,21 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
- EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
- isc::Unexpected);
+ EXPECT_THROW(srv.createNameChangeRequests(answer), isc::Unexpected);
}
-// Test no NameChangeRequests are added if FQDN option is NULL.
+// Test no NameChangeRequests if Client FQDN is not added to the server's
+// response.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
NakedDhcpv6Srv srv(0);
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
- // Pass NULL FQDN option. No NameChangeRequests should be created.
- Option6ClientFqdnPtr fqdn;
- ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
// There should be no new NameChangeRequests.
EXPECT_TRUE(srv.name_change_reqs_.empty());
@@ -485,20 +498,21 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
// Create Reply message with Client Id and Server id.
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+ // Add Client FQDN option.
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
- ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
// We didn't add any IAs, so there should be no NameChangeRequests in th
// queue.
ASSERT_TRUE(srv.name_change_reqs_.empty());
}
-// Test that a number of NameChangeRequests is created as a result of
-// processing the answer message which holds 3 IAs and when FQDN is
-// specified.
+// Test that exactly one NameChangeRequest is created as a result of processing
+// the answer message which holds 3 IAs and when FQDN is specified.
TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
NakedDhcpv6Srv srv(0);
@@ -516,33 +530,19 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
"MYHOST.EXAMPLE.COM",
Option6ClientFqdn::FULL);
+ answer->addOption(fqdn);
- // Create NameChangeRequests. Since we have added 3 IAs, it should
- // result in generation of 3 distinct NameChangeRequests.
- ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
- ASSERT_EQ(3, srv.name_change_reqs_.size());
-
- // Verify that NameChangeRequests are correct. Each call to the
- // verifyNameChangeRequest will pop verified request from the queue.
+ // Create NameChangeRequest for the first allocated address.
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ // Verify that NameChangeRequest is correct.
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1::1",
"000201415AA33D1187D148275136FA30300478"
"FAAAA3EBD29826B5C907B2C9268A6F52",
0, 500);
- verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
- "2001:db8:1::2",
- "000201415AA33D1187D148275136FA30300478"
- "FAAAA3EBD29826B5C907B2C9268A6F52",
- 0, 500);
-
- verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
- "2001:db8:1::3",
- "000201415AA33D1187D148275136FA30300478"
- "FAAAA3EBD29826B5C907B2C9268A6F52",
- 0, 500);
-
}
// Test creation of the NameChangeRequest to remove both forward and reverse
@@ -642,7 +642,8 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
// Create a Solicit message with FQDN option and generate server's
// response using processSolicit function.
- testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
+ testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
+ "myhost.example.com.", srv);
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
@@ -657,7 +658,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
// response using processRequest function. This will result in the
// creation of a new lease and the appropriate NameChangeRequest
// to add both reverse and forward mapping to DNS.
- testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
@@ -672,7 +674,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
// entries should be removed and the entries for the new domain-name
// should be added. Therefore, we expect two NameChangeRequests. One to
// remove the existing entries, one to add new entries.
- testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
+ testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
+ "otherhost.example.com.", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
@@ -687,6 +690,38 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
}
+// Test that NameChangeRequest is not generated when Solicit message is sent.
+// The Solicit is here sent after a lease has been allocated for a client.
+// The Solicit conveys a different hostname which would trigger updates to
+// DNS if the Request was sent instead of Soicit. The code should differentiate
+// behavior depending whether Solicit or Request is sent.
+TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // When the returning client sends Solicit the code should never generate
+ // NameChangeRequest and preserve existing DNS entries for the client.
+ // The NameChangeRequest should only be generated when a client sends
+ // Request or Renew.
+ testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
+ "otherhost.example.com.", srv);
+ ASSERT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+
// Test that client may send Request followed by the Renew, both holding
// FQDN options, but each option holding different domain-name. The Renew
// should result in generation of the two NameChangeRequests, one to remove
@@ -699,7 +734,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
// response using processRequest function. This will result in the
// creation of a new lease and the appropriate NameChangeRequest
// to add both reverse and forward mapping to DNS.
- testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
@@ -714,7 +750,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
// entries should be removed and the entries for the new domain-name
// should be added. Therefore, we expect two NameChangeRequests. One to
// remove the existing entries, one to add new entries.
- testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
+ testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
+ "otherhost.example.com.", srv);
ASSERT_EQ(2, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
@@ -736,7 +773,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
// response using processRequest function. This will result in the
// creation of a new lease and the appropriate NameChangeRequest
// to add both reverse and forward mapping to DNS.
- testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
@@ -748,7 +786,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
// removed and all existing DNS entries for this lease should be
// also removed. Therefore, we expect that single NameChangeRequest to
// remove DNS entries is generated.
- testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
+ testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
+ "otherhost.example.com.", srv);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
"2001:db8:1:1::dead:beef",
@@ -758,15 +797,16 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
}
-// Checks that the server does not include DHCPv6 Client FQDN option in its
-// response when client doesn't include ORO option in the Request.
+// Checks that the server include DHCPv6 Client FQDN option in its
+// response even when client doesn't request this option using ORO.
TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
NakedDhcpv6Srv srv(0);
// The last parameter disables use of the ORO to request FQDN option
// In this case, we expect that the FQDN option will not be included
// in the server's response. The testProcessMessage will check that.
- testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv, false);
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv, false);
ASSERT_EQ(1, srv.name_change_reqs_.size());
verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
"2001:db8:1:1::dead:beef",
@@ -775,4 +815,92 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
0, 4000);
}
+// Checks that FQDN is generated from an ip address, when client sends an empty
+// FQDN.
+TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
+ NakedDhcpv6Srv srv(0);
+
+ testProcessMessage(DHCPV6_REQUEST, "",
+ "host-2001-db8-1-1--dead-beef.example.com.",
+ srv, false);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "0002018D6874B105A5C92DBBD6E4F6C80A93161"
+ "BC03996F0CD0EB75800DEF997C29961",
+ 0, 4000);
+
+}
+
+// Checks that when the server reuses expired lease, the NameChangeRequest
+// is generated to remove the DNS mapping for the expired lease and second
+// NameChangeRequest to add a DNS mapping for a new lease.
+TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
+ // This address will be used throughout the test.
+ IOAddress addr("2001:db8:1:1::dead:beef");
+ // We are going to configure a subnet with a pool that consists of
+ // exactly one address. This address will be handed out to the
+ // client, will get expired and then be reused.
+ CfgMgr::instance().deleteSubnets6();
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1:1::"), 56, 1, 2,
+ 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr));
+ subnet_->addPool(pool_);
+ CfgMgr::instance().addSubnet6(subnet_);
+
+ // Allocate a lease.
+ NakedDhcpv6Srv srv(0);
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+ "myhost.example.com.", srv);
+ // Test that the appropriate NameChangeRequest has been generated.
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4);
+ // Get the lease acquired and modify it. In particular, expire it.
+ Lease6Ptr lease =
+ LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(lease);
+ // One of the following: IAID, DUID or subnet identifier has to be changed
+ // because otherwise the allocation engine will treat the lease as
+ // being renewed by the same client. If we at least change subnet identifier
+ // the lease will be treated as expired lease to be reused.
+ ++lease->subnet_id_;
+
+ // Move the cllt back in time and make sure that the lease got expired.
+ lease->cltt_ = time(NULL) - 10;
+ lease->valid_lft_ = 5;
+ ASSERT_TRUE(lease->expired());
+ // Change the hostname so as the name change request for removing existing
+ // DNS mapping is generated.
+ lease->hostname_ = "otherhost.example.com.";
+ // Update the lease in the lease database.
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ // Simulate another lease acquisition. Since, our pool consists of
+ // exactly one address and this address is used by the lease in the
+ // lease database, it is guaranteed that the allocation engine will
+ // reuse this lease.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
+ "myhost.example.com.", srv);
+ ASSERT_EQ(2, srv.name_change_reqs_.size());
+ // The first name change request generated, should remove a DNS
+ // mapping for an expired lease.
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD2"
+ "65FCEA97F93623019B2E0D14E5323D5A",
+ 0, 5);
+ // The second name change request should add a DNS mapping for
+ // a new lease.
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4);
+
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/wireshark.cc b/src/bin/dhcp6/tests/wireshark.cc
index 4e96548129..cd2909673e 100644
--- a/src/bin/dhcp6/tests/wireshark.cc
+++ b/src/bin/dhcp6/tests/wireshark.cc
@@ -17,9 +17,9 @@
#include <string>
/// @file wireshark.cc
-///
+///
/// @brief contains packet captures imported from Wireshark
-///
+///
/// These are actual packets captured over wire. They are used in various
/// tests.
///
@@ -33,6 +33,10 @@
/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
/// 7. Make sure you decribe the capture appropriately
/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
+/// 9. To easily copy packet description, click File... -> Extract packet
+/// dissections -> as plain text file...
+/// (Make sure that the packet is expanded in the view. The text file will
+/// contain whatever expansion level you have in the graphical tree.)
using namespace std;
@@ -81,7 +85,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
// - ORO (7)
// string exported from Wireshark
- string hex_string =
+ string hex_string =
"0c0500000000000000000000000000000000fc00000000000000000000000000000900"
"12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
"000000010000000000000000000600020007";
@@ -102,7 +106,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
// This is an actual DOCSIS packet
- // RELAY-FORW (12)
+ // RELAY-FORW (12)
// - Relay Message
// - SOLICIT (1)
// - client-id
@@ -132,7 +136,7 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
// - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
// string exported from Wireshark
- string hex_string =
+ string hex_string =
"0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
"090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
"000000050018000000000000000000000000000000000000000000000000000e000000"
@@ -159,5 +163,141 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
return (pkt);
}
+/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter)
+Pkt6Ptr isc::test::Dhcpv6SrvTest::captureeRouterRelayedSolicit() {
+
+/* Packet description exported from wireshark:
+DHCPv6
+ Message type: Relay-forw (12)
+ Hopcount: 0
+ Link address: 2001:558:ffa8::1 (2001:558:ffa8::1)
+ Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515)
+ Relay Message
+ Option: Relay Message (9)
+ Length: 241
+ Value: 01a90044000e000000140000000600080011001700180019...
+ DHCPv6
+ Message type: Solicit (1)
+ Transaction ID: 0xa90044
+ Rapid Commit
+ Option: Rapid Commit (14)
+ Length: 0
+ Reconfigure Accept
+ Option: Reconfigure Accept (20)
+ Length: 0
+ Option Request
+ Option: Option Request (6)
+ Length: 8
+ Value: 0011001700180019
+ Requested Option code: Vendor-specific Information (17)
+ Requested Option code: DNS recursive name server (23)
+ Requested Option code: Domain Search List (24)
+ Requested Option code: Identity Association for Prefix Delegation (25)
+ Vendor Class
+ Option: Vendor Class (16)
+ Length: 16
+ Value: 0000118b000a65526f75746572312e30
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ vendor-class-data: eRouter1.0
+ Vendor-specific Information
+ Option: Vendor-specific Information (17)
+ Length: 112
+ Value: 0000118b0002000745524f555445520003000b45434d3a45...
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption: Device Type = (2)"EROUTER"
+ Suboption: Embedded Components = (3)"ECM:EROUTER"
+ Suboption: Serial Number = (4)"2BR229U40044C"
+ Suboption: Hardware Version = (5)"1.04"
+ Suboption: Software Version = (6)"V1.33.03"
+ Suboption: Boot ROM Version = (7)"2.3.0R2"
+ Suboption: Organization Unique Identifier = (8)"00095B"
+ Suboption: Model Number = (9)"CG3000DCR"
+ Suboption: Vendor Name = (10)"Netgear"
+ Client Identifier
+ Option: Client Identifier (1)
+ Length: 10
+ Value: 0003000120e52ab81515
+ DUID: 0003000120e52ab81515
+ DUID Type: link-layer address (3)
+ Hardware type: Ethernet (1)
+ Link-layer address: 20:e5:2a:b8:15:15
+ Identity Association for Prefix Delegation
+ Option: Identity Association for Prefix Delegation (25)
+ Length: 41
+ Value: 2ab815150000000000000000001a00190000000000000000...
+ IAID: 2ab81515
+ T1: 0
+ T2: 0
+ IA Prefix
+ Option: IA Prefix (26)
+ Length: 25
+ Value: 000000000000000038000000000000000000000000000000...
+ Preferred lifetime: 0
+ Valid lifetime: 0
+ Prefix length: 56
+ Prefix address: :: (::)
+ Identity Association for Non-temporary Address
+ Option: Identity Association for Non-temporary Address (3)
+ Length: 12
+ Value: 2ab815150000000000000000
+ IAID: 2ab81515
+ T1: 0
+ T2: 0
+ Elapsed time
+ Option: Elapsed time (8)
+ Length: 2
+ Value: 0000
+ Elapsed time: 0 ms
+ Vendor-specific Information
+ Option: Vendor-specific Information (17)
+ Length: 22
+ Value: 0000118b0402000620e52ab815140401000401020300
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption: CM MAC Address Option = (1026)20:e5:2a:b8:15:14
+ Suboption: CMTS Capabilities Option : (1025)
+ Interface-Id
+ Option: Interface-Id (18)
+ Length: 4
+ Value: 00000022
+ Interface-ID:
+ Remote Identifier
+ Option: Remote Identifier (37)
+ Length: 14
+ Value: 0000101300015c228d4110000122
+ Enterprise ID: Arris Interactive LLC (4115)
+ Remote-ID: 00015c228d4110000122
+ DHCP option 53
+ Option: Unknown (53)
+ Length: 10
+ Value: 0003000100015c228d3d
+ DUID: 0003000100015c228d3d
+ DUID Type: link-layer address (3)
+ Hardware type: Ethernet (1)
+ Link-layer address: 00:01:5c:22:8d:3d */
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500"
+ "0900f101a90044000e000000140000000600080011001700180019001000100000118b"
+ "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b"
+ "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30"
+ "340006000856312e33332e303300070007322e332e3052320008000630303039354200"
+ "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8"
+ "1515001900292ab815150000000000000000001a001900000000000000003800000000"
+ "0000000000000000000000000003000c2ab81515000000000000000000080002000000"
+ "1100160000118b0402000620e52ab81514040100040102030000120004000000220025"
+ "000e0000101300015c228d41100001220035000a0003000100015c228d3d";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+ return (pkt);
+}
+
}; // end of isc::test namespace
}; // end of isc namespace
diff --git a/src/bin/resolver/.gitignore b/src/bin/resolver/.gitignore
index b3abbc9b7c..a4c02b0a7b 100644
--- a/src/bin/resolver/.gitignore
+++ b/src/bin/resolver/.gitignore
@@ -6,3 +6,4 @@
/spec_config.h
/spec_config.h.pre
/b10-resolver.8
+/s-messages
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 4158638456..1405cd83a4 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -373,10 +373,7 @@ class MockXfrinConnection(XfrinConnection):
[resp.add_rrset(Message.SECTION_AUTHORITY, a) for a in authorities]
renderer = MessageRenderer()
- if tsig_ctx is not None:
- resp.to_wire(renderer, tsig_ctx)
- else:
- resp.to_wire(renderer)
+ resp.to_wire(renderer, tsig_ctx)
reply_data = struct.pack('H', socket.htons(renderer.get_length()))
reply_data += renderer.get_data()
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index a894d550f7..b8b457e145 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -727,8 +727,9 @@ class XfrinConnection(asyncore.dispatcher):
self.connect(self._master_addrinfo[2])
return True
except socket.error as e:
- logger.error(XFRIN_CONNECT_MASTER, self.tsig_key_name,
+ logger.error(XFRIN_CONNECT_MASTER,
self._master_addrinfo[2],
+ self._zone_name.to_text(),
str(e))
return False
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 4383324f79..6dc719f952 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -56,9 +56,9 @@ most likely cause is that xfrin the msgq daemon is not running.
There was an error while the given command was being processed. The
error is given in the log message.
-% XFRIN_CONNECT_MASTER (with TSIG %1) error connecting to master at %2: %3
-There was an error opening a connection to the master. The error is
-shown in the log message.
+% XFRIN_CONNECT_MASTER error connecting to master at %1 for %2: %3
+There was an error opening a connection to the master for the specified
+zone. The error is shown in the log message.
% XFRIN_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
Configuration for the global data sources is updated, but the update
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index aa84587785..0fe8e1511b 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -143,11 +143,11 @@ def format_addrinfo(addrinfo):
raise TypeError("addrinfo argument to format_addrinfo() does not "
"appear to be consisting of (family, socktype, (addr, port))")
+# This function is not inlined as it is replaced with a mock function
+# during testing.
def get_rrset_len(rrset):
"""Returns the wire length of the given RRset"""
- bytes = bytearray()
- rrset.to_wire(bytes)
- return len(bytes)
+ return rrset.get_length()
def get_soa_serial(soa_rdata):
'''Extract the serial field of an SOA RDATA and returns it as an Serial object.
@@ -345,12 +345,7 @@ class XfroutSession():
render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
- # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
- # we should remove the if statement and use a universal interface later.
- if tsig_ctx is not None:
- msg.to_wire(render, tsig_ctx)
- else:
- msg.to_wire(render)
+ msg.to_wire(render, tsig_ctx)
header_len = struct.pack('H', socket.htons(render.get_length()))
self._send_data(sock_fd, header_len)
diff --git a/src/hooks/dhcp/user_chk/Makefile.am b/src/hooks/dhcp/user_chk/Makefile.am
index 6b68f380b3..a7165d85d2 100644
--- a/src/hooks/dhcp/user_chk/Makefile.am
+++ b/src/hooks/dhcp/user_chk/Makefile.am
@@ -31,7 +31,8 @@ EXTRA_DIST = libdhcp_user_chk.dox
#CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
CLEANFILES = *.gcno *.gcda
-lib_LTLIBRARIES = libdhcp_user_chk.la
+nodistdir=$(abs_top_builddir)/src/hooks/dhcp/user_chk
+nodist_LTLIBRARIES = libdhcp_user_chk.la
libdhcp_user_chk_la_SOURCES =
libdhcp_user_chk_la_SOURCES += load_unload.cc
libdhcp_user_chk_la_SOURCES += pkt_receive_co.cc
diff --git a/src/hooks/dhcp/user_chk/tests/.gitignore b/src/hooks/dhcp/user_chk/tests/.gitignore
new file mode 100644
index 0000000000..32b5d22615
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/.gitignore
@@ -0,0 +1,2 @@
+/libdhcp_user_chk_unittests
+/test_data_files_config.h
diff --git a/src/lib/acl/ip_check.cc b/src/lib/acl/ip_check.cc
index 719206427c..c1f590f9f2 100644
--- a/src/lib/acl/ip_check.cc
+++ b/src/lib/acl/ip_check.cc
@@ -115,7 +115,7 @@ namespace {
const uint8_t*
getSockAddrData(const struct sockaddr& sa) {
const void* sa_ptr = &sa;
- const void* data_ptr;
+ const void* data_ptr = NULL;
if (sa.sa_family == AF_INET) {
const struct sockaddr_in* sin =
static_cast<const struct sockaddr_in*>(sa_ptr);
diff --git a/src/lib/asiodns/.gitignore b/src/lib/asiodns/.gitignore
index dedf17ea1f..189824e16d 100644
--- a/src/lib/asiodns/.gitignore
+++ b/src/lib/asiodns/.gitignore
@@ -1,2 +1,3 @@
/asiodns_messages.cc
/asiodns_messages.h
+/s-messages
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index a09d8df042..aef7c5771b 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -162,7 +162,7 @@ struct IOFetchData {
// we sent.
bool responseOK() {
return (*remote_snd == *remote_rcv && cumulative >= 2 &&
- readUint16(received->getData()) == qid);
+ readUint16(received->getData(), received->getLength()) == qid);
}
};
diff --git a/src/lib/asiodns/tests/io_fetch_unittest.cc b/src/lib/asiodns/tests/io_fetch_unittest.cc
index acb184ca1a..7f79a528f8 100644
--- a/src/lib/asiodns/tests/io_fetch_unittest.cc
+++ b/src/lib/asiodns/tests/io_fetch_unittest.cc
@@ -280,7 +280,8 @@ public:
cumulative_ += length;
bool complete = false;
if (cumulative_ > 2) {
- uint16_t dns_length = readUint16(receive_buffer_);
+ uint16_t dns_length = readUint16(receive_buffer_,
+ sizeof(receive_buffer_));
complete = ((dns_length + 2) == cumulative_);
}
@@ -310,7 +311,10 @@ public:
send_buffer_.clear();
send_buffer_.push_back(0);
send_buffer_.push_back(0);
- writeUint16(return_data_.size(), &send_buffer_[0]);
+ // send_buffer_.capacity() seems more logical below, but the
+ // code above fills in the first two bytes and size() becomes 2
+ // (sizeof uint16_t).
+ writeUint16(return_data_.size(), &send_buffer_[0], send_buffer_.size());
copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_));
if (return_data_.size() >= 2) {
send_buffer_[2] = qid_0;
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
index c4644c5048..71d3a4ca70 100644
--- a/src/lib/asiolink/dummy_io_cb.h
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -17,6 +17,8 @@
#include <iostream>
+#include <exceptions/exceptions.h>
+
#include <asio/error.hpp>
#include <asio/error_code.hpp>
@@ -39,20 +41,30 @@ public:
/// \brief Asynchronous I/O callback method
///
- /// TODO: explain why this method should never be called.
- /// This should be unused.
- void operator()(asio::error_code)
- {
- // TODO: log an error if this method ever gets called.
+ /// Should never be called, as this class is a convenience class provided
+ /// for instances where a socket is required but it is known that no
+ /// asynchronous operations will be carried out.
+ void operator()(asio::error_code) {
+ // If the function is called, there is a serious logic error in
+ // the program (this class should not be used as the callback
+ // class). As the asiolink module is too low-level for logging
+ // errors, throw an exception.
+ isc_throw(isc::Unexpected,
+ "DummyIOCallback::operator() must not be called");
}
/// \brief Asynchronous I/O callback method
///
- /// TODO: explain why this method should never be called.
- /// This should be unused.
- void operator()(asio::error_code, size_t)
- {
- // TODO: log an error if this method ever gets called.
+ /// Should never be called, as this class is a convenience class provided
+ /// for instances where a socket is required but it is known that no
+ /// asynchronous operations will be carried out.
+ void operator()(asio::error_code, size_t) {
+ // If the function is called, there is a serious logic error in
+ // the program (this class should not be used as the callback
+ // class). As the asiolink module is too low-level for logging
+ // errors, throw an exception.
+ isc_throw(isc::Unexpected,
+ "DummyIOCallback::operator() must not be called");
}
};
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
index 90bdf57d29..6a6ac1855e 100644
--- a/src/lib/asiolink/io_address.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -100,19 +100,36 @@ IOAddress::getFamily() const {
}
}
-const asio::ip::address&
-IOAddress::getAddress() const {
- return asio_address_;
+bool
+IOAddress::isV6LinkLocal() const {
+ if (!asio_address_.is_v6()) {
+ return (false);
+ }
+ return (asio_address_.to_v6().is_link_local());
+}
+
+bool
+IOAddress::isV6Multicast() const {
+ if (!asio_address_.is_v6()) {
+ return (false);
+ }
+ return (asio_address_.to_v6().is_multicast());
}
IOAddress::operator uint32_t() const {
- if (getAddress().is_v4()) {
- return (getAddress().to_v4().to_ulong());
+ if (asio_address_.is_v4()) {
+ return (asio_address_.to_v4().to_ulong());
} else {
isc_throw(BadValue, "Can't convert " << toText()
<< " address to IPv4.");
}
}
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address) {
+ os << address.toText();
+ return (os);
+}
+
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 5b11b8784c..cb898e3afd 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -91,14 +91,6 @@ public:
/// \return A string representation of the address.
std::string toText() const;
- /// \brief Returns const reference to the underlying address object.
- ///
- /// This is useful, when access to interface offerted by
- // asio::ip::address_v4 and asio::ip::address_v6 is beneficial.
- ///
- /// \return A const reference to asio::ip::address object
- const asio::ip::address& getAddress() const;
-
/// \brief Returns the address family
///
/// \return AF_INET for IPv4 or AF_INET6 for IPv6.
@@ -118,6 +110,16 @@ public:
return (asio_address_.is_v6());
}
+ /// \brief checks whether and address is IPv6 and is link-local
+ ///
+ /// \return true if the address is IPv6 link-local, false otherwise
+ bool isV6LinkLocal() const;
+
+ /// \brief checks whether and address is IPv6 and is multicast
+ ///
+ /// \return true if the address is IPv6 multicast, false otherwise
+ bool isV6Multicast() const;
+
/// \brief Creates an address from over wire data.
///
/// \param family AF_NET for IPv4 or AF_NET6 for IPv6.
@@ -196,7 +198,7 @@ public:
///
/// \param other Address to compare against.
///
- /// See \ref smaller_than method for details.
+ /// See \ref lessThan method for details.
bool operator<(const IOAddress& other) const {
return (lessThan(other));
}
@@ -205,7 +207,7 @@ public:
///
/// \param other Address to compare against.
///
- /// See \ref smaller_equal method for details.
+ /// See \ref smallerEqual method for details.
bool operator<=(const IOAddress& other) const {
return (smallerEqual(other));
}
@@ -232,6 +234,22 @@ private:
asio::ip::address asio_address_;
};
+/// \brief Insert the IOAddress as a string into stream.
+///
+/// This method converts the \c address into a string and inserts it
+/// into the output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described
+/// in ostream::operator<< but applied to \c IOAddress objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param address The \c IOAddress object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address);
+
} // namespace asiolink
} // namespace isc
#endif // IO_ADDRESS_H
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
index 2354521906..e6928f6976 100644
--- a/src/lib/asiolink/io_endpoint.cc
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -64,12 +64,12 @@ IOEndpoint::operator!=(const IOEndpoint& other) const {
ostream&
operator<<(ostream& os, const IOEndpoint& endpoint) {
if (endpoint.getFamily() == AF_INET6) {
- os << "[" << endpoint.getAddress().toText() << "]";
+ os << "[" << endpoint.getAddress() << "]";
} else {
// In practice this should be AF_INET, but it's not guaranteed by
// the interface. We'll use the result of textual address
// representation opaquely.
- os << endpoint.getAddress().toText();
+ os << endpoint.getAddress();
}
os << ":" << boost::lexical_cast<string>(endpoint.getPort());
return (os);
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index 7df4a80c9e..18eca95b56 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -358,7 +358,7 @@ TCPSocket<C>::processReceivedData(const void* staging, size_t length,
}
// Have enough data to interpret the packet count, so do so now.
- expected = isc::util::readUint16(data);
+ expected = isc::util::readUint16(data, cumulative);
// We have two bytes less of data to process. Point to the start of the
// data and adjust the packet size. Note that at this point,
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 8525c2ae74..222ec46416 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -35,6 +35,7 @@ run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += local_socket_unittest.cc
+run_unittests_SOURCES += dummy_io_callback_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/asiolink/tests/dummy_io_callback_unittest.cc b/src/lib/asiolink/tests/dummy_io_callback_unittest.cc
new file mode 100644
index 0000000000..3acabfd78d
--- /dev/null
+++ b/src/lib/asiolink/tests/dummy_io_callback_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+
+#include <asiolink/dummy_io_cb.h>
+
+using namespace isc::asiolink;
+using namespace asio;
+
+namespace { // begin unnamed namespace
+
+TEST(DummyIOCallbackTest, throws) {
+ DummyIOCallback cb;
+ asio::error_code error_code;
+
+ // All methods should throw isc::Unexpected.
+ EXPECT_THROW(cb(error_code), isc::Unexpected);
+ EXPECT_THROW(cb(error_code, 42), isc::Unexpected);
+}
+
+} // end of unnamed namespace
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
index 4bd7626df3..221ed5e471 100644
--- a/src/lib/asiolink/tests/io_address_unittest.cc
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstring>
#include <vector>
+#include <sstream>
using namespace isc::asiolink;
@@ -83,7 +84,7 @@ TEST(IOAddressTest, fromBytes) {
EXPECT_NO_THROW({
addr = IOAddress::fromBytes(AF_INET, v4);
});
- EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
+ EXPECT_EQ(addr, IOAddress("192.0.2.3"));
}
TEST(IOAddressTest, toBytesV4) {
@@ -172,3 +173,47 @@ TEST(IOAddressTest, lessThanEqual) {
EXPECT_TRUE(addr6 <= addr7);
}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(IOAddressTest, LeftShiftOperator) {
+ const IOAddress addr("192.0.2.5");
+
+ std::ostringstream oss;
+ oss << addr;
+ EXPECT_EQ(addr.toText(), oss.str());
+}
+
+// Tests address classification methods (which were previously used by accessing
+// underlying asio objects directly)
+TEST(IOAddressTest, accessClassificationMethods) {
+ IOAddress addr1("192.0.2.5"); // IPv4
+ IOAddress addr2("::"); // IPv6
+ IOAddress addr3("2001:db8::1"); // global IPv6
+ IOAddress addr4("fe80::1234"); // link-local
+ IOAddress addr5("ff02::1:2"); // multicast
+
+ EXPECT_TRUE (addr1.isV4());
+ EXPECT_FALSE(addr1.isV6());
+ EXPECT_FALSE(addr1.isV6LinkLocal());
+ EXPECT_FALSE(addr1.isV6Multicast());
+
+ EXPECT_FALSE(addr2.isV4());
+ EXPECT_TRUE (addr2.isV6());
+ EXPECT_FALSE(addr2.isV6LinkLocal());
+ EXPECT_FALSE(addr2.isV6Multicast());
+
+ EXPECT_FALSE(addr3.isV4());
+ EXPECT_TRUE (addr3.isV6());
+ EXPECT_FALSE(addr3.isV6LinkLocal());
+ EXPECT_FALSE(addr3.isV6Multicast());
+
+ EXPECT_FALSE(addr4.isV4());
+ EXPECT_TRUE (addr4.isV6());
+ EXPECT_TRUE (addr4.isV6LinkLocal());
+ EXPECT_FALSE(addr4.isV6Multicast());
+
+ EXPECT_FALSE(addr5.isV4());
+ EXPECT_TRUE (addr5.isV6());
+ EXPECT_FALSE(addr5.isV6LinkLocal());
+ EXPECT_TRUE (addr5.isV6Multicast());
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
index 9de366c851..b0adf2eae8 100644
--- a/src/lib/asiolink/tests/tcp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -227,7 +227,7 @@ serverRead(tcp::socket& socket, TCPCallback& server_cb) {
// If we have read at least two bytes, we can work out how much we
// should be reading.
if (server_cb.cumulative() >= 2) {
- server_cb.expected() = readUint16(server_cb.data());
+ server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
if ((server_cb.expected() + 2) == server_cb.cumulative()) {
// Amount of data read from socket equals the size of the
@@ -261,7 +261,7 @@ TEST(TCPSocket, processReceivedData) {
}
// Check that the method will handle various receive sizes.
- writeUint16(PACKET_SIZE, inbuff);
+ writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
cumulative = 0;
offset = 0;
@@ -317,7 +317,7 @@ TEST(TCPSocket, processReceivedData) {
// Tests the operation of a TCPSocket by opening it, sending an asynchronous
// message to a server, receiving an asynchronous message from the server and
// closing.
-TEST(TCPSocket, SequenceTest) {
+TEST(TCPSocket, sequenceTest) {
// Common objects.
IOService service; // Service object for async control
@@ -408,7 +408,7 @@ TEST(TCPSocket, SequenceTest) {
server_cb.length() = 0;
server_cb.cumulative() = 0;
- writeUint16(sizeof(INBOUND_DATA), server_cb.data());
+ writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TCPCallback::MIN_SIZE);
copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
(server_cb.data() + 2));
server_socket.async_send(asio::buffer(server_cb.data(),
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
index 3d918748a7..be62590630 100644
--- a/src/lib/asiolink/tests/udp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -188,7 +188,7 @@ TEST(UDPSocket, processReceivedData) {
// two bytes of the buffer.
uint16_t count = 0;
for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
- writeUint16(count, inbuff);
+ writeUint16(count, inbuff, sizeof(inbuff));
// Set some random values
cumulative = 5;
diff --git a/src/lib/cache/.gitignore b/src/lib/cache/.gitignore
index a33f3f03c4..65ae291192 100644
--- a/src/lib/cache/.gitignore
+++ b/src/lib/cache/.gitignore
@@ -1,2 +1,3 @@
/cache_messages.cc
/cache_messages.h
+/s-messages
diff --git a/src/lib/cc/.gitignore b/src/lib/cc/.gitignore
index d1e56dfa5c..d375181484 100644
--- a/src/lib/cc/.gitignore
+++ b/src/lib/cc/.gitignore
@@ -3,3 +3,4 @@
/proto_defs.h
/session_config.h
/session_config.h.pre
+/s-messages
diff --git a/src/lib/cc/session.h b/src/lib/cc/session.h
index 63bb41c556..e9a3375c47 100644
--- a/src/lib/cc/session.h
+++ b/src/lib/cc/session.h
@@ -163,7 +163,7 @@ namespace isc {
/// @brief returns socket descriptor from underlying socket connection
///
- /// @param returns socket descriptor used for session connection
+ /// @return socket descriptor used for session connection
virtual int getSocketDesc() const;
private:
// The following two methods are virtual to allow tests steal and
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index fa8e5fc040..4f1057c1e7 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -84,7 +84,7 @@ TEST(Element, from_and_to_json) {
sv.push_back("\"asdf\"");
sv.push_back("null");
sv.push_back("[ 1, 2, 3, 4 ]");
- sv.push_back("{ \"name\": \"foo\", \"value\": 47806 }");
+ sv.push_back("{ \"name\": \"foo\", \"value\": 56176 }");
sv.push_back("[ { \"a\": 1, \"b\": \"c\" }, { \"a\": 2, \"b\": \"d\" } ]");
sv.push_back("8.23");
sv.push_back("123.456");
@@ -570,12 +570,12 @@ TEST(Element, ListElement) {
el->set(0, Element::fromJSON("\"foo\""));
EXPECT_EQ(el->get(0)->stringValue(), "foo");
- el->add(Element::create(47806));
- EXPECT_EQ(el->get(3)->intValue(), 47806);
+ el->add(Element::create(56176));
+ EXPECT_EQ(el->get(3)->intValue(), 56176);
el->remove(1);
el->remove(1);
- EXPECT_EQ(el->str(), "[ \"foo\", 47806 ]");
+ EXPECT_EQ(el->str(), "[ \"foo\", 56176 ]");
// hmm, it errors on EXPECT_THROW(el->get(3), std::out_of_range)
EXPECT_ANY_THROW(el->get(3));
@@ -600,8 +600,8 @@ TEST(Element, MapElement) {
EXPECT_TRUE(isNull(el->get("value3")));
- el->set("value3", Element::create(47806));
- EXPECT_EQ(el->get("value3")->intValue(), 47806);
+ el->set("value3", Element::create(56176));
+ EXPECT_EQ(el->get("value3")->intValue(), 56176);
el->remove("value3");
EXPECT_TRUE(isNull(el->get("value3")));
diff --git a/src/lib/config/.gitignore b/src/lib/config/.gitignore
index c7ec9d3565..d666f2469a 100644
--- a/src/lib/config/.gitignore
+++ b/src/lib/config/.gitignore
@@ -1,2 +1,3 @@
/config_messages.cc
/config_messages.h
+/s-messages
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index bcc97de409..7900aa9c27 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -110,8 +110,8 @@ public:
isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
bool recurse = false) const;
- /// Returns a map of the top-level configuration items, as currently
- /// set or their defaults
+ /// \brief Returns a map of the top-level configuration items, as
+ /// currently set or their defaults.
///
/// \return An ElementPtr pointing to a MapElement containing
/// the top-level configuration items
diff --git a/src/lib/config/tests/testdata/spec22.spec b/src/lib/config/tests/testdata/spec22.spec
index cccd77bfae..687db08bee 100644
--- a/src/lib/config/tests/testdata/spec22.spec
+++ b/src/lib/config/tests/testdata/spec22.spec
@@ -102,7 +102,7 @@
"item_name": "v92b",
"item_type": "integer",
"item_optional": false,
- "item_default": 47806
+ "item_default": 56176
}
]
}
diff --git a/src/lib/config/tests/testdata/spec27.spec b/src/lib/config/tests/testdata/spec27.spec
index 0b246b90b0..2c73021472 100644
--- a/src/lib/config/tests/testdata/spec27.spec
+++ b/src/lib/config/tests/testdata/spec27.spec
@@ -107,7 +107,7 @@
"item_name": "v92b",
"item_type": "integer",
"item_optional": false,
- "item_default": 47806
+ "item_default": 56176
}
]
}
diff --git a/src/lib/datasrc/.gitignore b/src/lib/datasrc/.gitignore
index 4b199ed505..0d473f3352 100644
--- a/src/lib/datasrc/.gitignore
+++ b/src/lib/datasrc/.gitignore
@@ -5,3 +5,5 @@
/static.zone
/sqlite3_datasrc_messages.cc
/sqlite3_datasrc_messages.h
+/s-messages1
+/s-messages2
diff --git a/src/lib/datasrc/cache_config.cc b/src/lib/datasrc/cache_config.cc
index ec3cfeba82..9a44900606 100644
--- a/src/lib/datasrc/cache_config.cc
+++ b/src/lib/datasrc/cache_config.cc
@@ -75,7 +75,8 @@ CacheConfig::CacheConfig(const std::string& datasrc_type,
if (!enabled_) {
isc_throw(CacheConfigError,
- "The cache must be enabled for the MasterFiles type");
+ "The cache must be enabled for the MasterFiles type: "
+ << datasrc_conf);
}
typedef std::map<std::string, ConstElementPtr> ZoneToFile;
@@ -100,7 +101,8 @@ CacheConfig::CacheConfig(const std::string& datasrc_type,
if (!datasrc_conf.contains("cache-zones")) {
isc_throw(NotImplemented, "Auto-detection of zones "
"to cache is not yet implemented, supply "
- "cache-zones parameter");
+ "cache-zones parameter: "
+ << datasrc_conf);
// TODO: Auto-detect list of all zones in the
// data source.
}
diff --git a/src/lib/datasrc/cache_config.h b/src/lib/datasrc/cache_config.h
index c2210666e7..cfcdc7b58c 100644
--- a/src/lib/datasrc/cache_config.h
+++ b/src/lib/datasrc/cache_config.h
@@ -170,7 +170,7 @@ public:
/// \param zone_name The origin name of the zone
/// \return A \c LoadAction functor to load zone data or an empty functor
/// (see above).
- memory::LoadAction getLoadAction(const dns::RRClass& rrlcass,
+ memory::LoadAction getLoadAction(const dns::RRClass& rrclass,
const dns::Name& zone_name) const;
/// \brief Read only iterator type over configured cached zones.
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index 77c2fd5bba..31cb6c25c5 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -316,7 +316,7 @@ typedef boost::shared_ptr<ClientList> ClientListPtr;
typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
/// \brief Concrete implementation of the ClientList, which is constructed
-/// based on configuration.
+/// based on configuration.
///
/// This is the implementation which is expected to be used in the servers.
/// However, it is expected most of the code will use it as the ClientList,
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 9c85054341..b6a3f3c416 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -103,7 +103,7 @@ DatabaseClient::findZone(const Name& name) const {
}
// Then super domains
// Start from 1, as 0 is covered above
- for (size_t i(1); i < name.getLabelCount(); ++i) {
+ for (size_t i = 1; i < name.getLabelCount(); ++i) {
isc::dns::Name superdomain(name.split(i));
zone = accessor_->getZone(superdomain.toText());
if (zone.first) {
@@ -344,7 +344,7 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
}
if (!sig_store.empty()) {
// Add signatures to all found RRsets
- for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
+ for (std::map<RRType, RRsetPtr>::iterator i = result.begin();
i != result.end(); ++ i) {
sig_store.appendSignatures(i->second);
}
@@ -646,7 +646,7 @@ DatabaseClient::Finder::findWildcardMatch(
// Strip off the left-more label(s) in the name and replace with a "*".
const Name superdomain(name.split(i));
- const string wildcard("*." + superdomain.toText());
+ const string wildcard(Name("*", 1, &superdomain).toText());
const string construct_name(name.toText());
// TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
@@ -760,7 +760,7 @@ DatabaseClient::Finder::FindDNSSECContext::probe() {
const string origin = finder_.getOrigin().toText();
const FoundRRsets nsec3_found =
finder_.getRRsets(origin, NSEC3PARAM_TYPES(), true);
- const FoundIterator nfi=
+ const FoundIterator nfi =
nsec3_found.second.find(RRType::NSEC3PARAM());
is_nsec3_ = (nfi != nsec3_found.second.end());
@@ -908,7 +908,7 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
if (any) {
// An ANY query, copy everything to the target instead of returning
// directly.
- for (FoundIterator it(found.second.begin());
+ for (FoundIterator it = found.second.begin();
it != found.second.end(); ++it) {
if (it->second) {
// Skip over the empty ANY
@@ -1110,7 +1110,7 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
// We keep stripping the leftmost label until we find something.
// In case it is recursive, we'll exit the loop at the first iteration.
- for (unsigned labels(qlabels); labels >= olabels; -- labels) {
+ for (unsigned labels = qlabels; labels >= olabels; --labels) {
const string hash(calculator->calculate(labels == qlabels ? name :
name.split(qlabels - labels,
labels)));
diff --git a/src/lib/datasrc/memory/.gitignore b/src/lib/datasrc/memory/.gitignore
index fbb638179f..89122b9402 100644
--- a/src/lib/datasrc/memory/.gitignore
+++ b/src/lib/datasrc/memory/.gitignore
@@ -1,2 +1,3 @@
/memory_messages.cc
/memory_messages.h
+/s-messages
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 05d97950b1..f309e4a719 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -1206,6 +1206,10 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c DomainTree is allocated.
+ /// \param return_empty_node Whether \c find() on this tree should
+ /// return empty nodes (which contain no
+ /// data). If it's \c true, \c find() will
+ /// match and return empty nodes.
static DomainTree* create(util::MemorySegment& mem_sgmt,
bool return_empty_node = false)
{
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index 0b0c827a8c..98fbb572fe 100644
--- a/src/lib/datasrc/memory/memory_client.cc
+++ b/src/lib/datasrc/memory/memory_client.cc
@@ -90,6 +90,7 @@ private:
ZoneChain chain_;
const RdataSet* set_node_;
const RRClass rrclass_;
+ ConstRRsetPtr soa_;
const ZoneTree& tree_;
const ZoneNode* node_;
// Only used when separate_rrs_ is true
@@ -120,6 +121,18 @@ public:
isc_throw(Unexpected,
"In-memory zone corrupted, missing origin node");
}
+
+ if (node_) {
+ const RdataSet* origin_set = node_->getData();
+ if (origin_set) {
+ const RdataSet* soa_set = RdataSet::find(origin_set, RRType::SOA());
+ if (soa_set) {
+ soa_ = ConstRRsetPtr (new TreeNodeRRset(rrclass_, node_,
+ soa_set, true));
+ }
+ }
+ }
+
// Initialize the iterator if there's somewhere to point to
if (node_ != NULL && node_->getData() != NULL) {
set_node_ = node_->getData();
@@ -231,7 +244,7 @@ public:
}
virtual ConstRRsetPtr getSOA() const {
- isc_throw(NotImplemented, "Not implemented");
+ return (soa_);
}
};
diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h
index bd937816b6..a30ec0572f 100644
--- a/src/lib/datasrc/memory/rdataset.h
+++ b/src/lib/datasrc/memory/rdataset.h
@@ -300,7 +300,7 @@ public:
/// needs to be extended, unless there's a reason other than simply
/// because it's already a member function.
///
- /// \param rdata_head A pointer to \c RdataSet from which the search
+ /// \param rdataset_head A pointer to \c RdataSet from which the search
/// starts. It can be NULL.
/// \param type The RRType of \c RdataSet to find.
/// \param sigonly_ok Whether it should find an RdataSet that only has
diff --git a/src/lib/datasrc/memory/treenode_rrset.cc b/src/lib/datasrc/memory/treenode_rrset.cc
index 4734e04579..396bf87a51 100644
--- a/src/lib/datasrc/memory/treenode_rrset.cc
+++ b/src/lib/datasrc/memory/treenode_rrset.cc
@@ -101,6 +101,19 @@ TreeNodeRRset::toText() const {
namespace {
void
+sizeupName(const LabelSequence& name_labels, RdataNameAttributes,
+ size_t* length)
+{
+ *length += name_labels.getDataLength();
+}
+
+void
+sizeupData(const void*, size_t data_len, size_t* length)
+{
+ *length += data_len;
+}
+
+void
renderName(const LabelSequence& name_labels, RdataNameAttributes attr,
AbstractMessageRenderer* renderer)
{
@@ -114,6 +127,35 @@ renderData(const void* data, size_t data_len,
renderer->writeData(data, data_len);
}
+// Helper for calculating wire data length of a single (etiher main or
+// RRSIG) RRset.
+uint16_t
+getLengthHelper(size_t* rlength, size_t rr_count, uint16_t name_labels_size,
+ RdataReader& reader, bool (RdataReader::* rdata_iterate_fn)())
+{
+ uint16_t length = 0;
+
+ for (size_t i = 0; i < rr_count; ++i) {
+ size_t rrlen = 0;
+
+ rrlen += name_labels_size;
+ rrlen += 2; // TYPE field
+ rrlen += 2; // CLASS field
+ rrlen += 4; // TTL field
+ rrlen += 2; // RDLENGTH field
+
+ *rlength = 0;
+ const bool rendered = (reader.*rdata_iterate_fn)();
+ assert(rendered == true);
+
+ rrlen += *rlength;
+ assert(length + rrlen < 65536);
+ length += rrlen;
+ }
+
+ return (length);
+}
+
// Common code logic for rendering a single (either main or RRSIG) RRset.
size_t
writeRRs(AbstractMessageRenderer& renderer, size_t rr_count,
@@ -149,6 +191,39 @@ writeRRs(AbstractMessageRenderer& renderer, size_t rr_count,
}
}
+uint16_t
+TreeNodeRRset::getLength() const {
+ size_t rlength = 0;
+ RdataReader reader(rrclass_, rdataset_->type, rdataset_->getDataBuf(),
+ rdataset_->getRdataCount(), rrsig_count_,
+ boost::bind(sizeupName, _1, _2, &rlength),
+ boost::bind(sizeupData, _1, _2, &rlength));
+
+ // Get the owner name of the RRset in the form of LabelSequence.
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+ const LabelSequence name_labels = getOwnerLabels(labels_buf);
+ const uint16_t name_labels_size = name_labels.getDataLength();
+
+ // Find the length of the main (non RRSIG) RRs
+ const uint16_t rrset_length =
+ getLengthHelper(&rlength, rdataset_->getRdataCount(), name_labels_size,
+ reader, &RdataReader::iterateRdata);
+
+ rlength = 0;
+ const bool rendered = reader.iterateRdata();
+ assert(rendered == false); // we should've reached the end
+
+ // Find the length of any RRSIGs, if we supposed to do so
+ const uint16_t rrsig_length = dnssec_ok_ ?
+ getLengthHelper(&rlength, rrsig_count_, name_labels_size,
+ reader, &RdataReader::iterateSingleSig) : 0;
+
+ // the uint16_ts are promoted to ints during addition below, so it
+ // won't overflow a 16-bit register.
+ assert(rrset_length + rrsig_length < 65536);
+ return (rrset_length + rrsig_length);
+}
+
unsigned int
TreeNodeRRset::toWire(AbstractMessageRenderer& renderer) const {
RdataReader reader(rrclass_, rdataset_->type, rdataset_->getDataBuf(),
@@ -195,6 +270,11 @@ TreeNodeRRset::addRdata(const rdata::Rdata&) {
isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
}
+void
+TreeNodeRRset::addRdata(const std::string&) {
+ isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
+}
+
namespace {
// In this namespace we define a set of helper stuff to implement the
// RdataIterator for the TreeNodeRRset. We should eventually optimize
diff --git a/src/lib/datasrc/memory/treenode_rrset.h b/src/lib/datasrc/memory/treenode_rrset.h
index 640c9721dc..47d196eaf1 100644
--- a/src/lib/datasrc/memory/treenode_rrset.h
+++ b/src/lib/datasrc/memory/treenode_rrset.h
@@ -155,7 +155,7 @@ public:
node_(node), rdataset_(rdataset),
rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname)),
- ttl_data_(rdataset->getTTLData()), ttl_(NULL)
+ ttl_data_(rdataset->getTTLData()), ttl_(NULL)
{}
virtual ~TreeNodeRRset() {
@@ -168,6 +168,8 @@ public:
return (rdataset_->getRdataCount());
}
+ virtual uint16_t getLength() const;
+
virtual const dns::Name& getName() const;
virtual const dns::RRClass& getClass() const {
return (rrclass_);
@@ -204,6 +206,11 @@ public:
/// It throws \c isc::Unexpected unconditionally.
virtual void addRdata(const dns::rdata::Rdata& rdata);
+ /// \brief Specialized version of \c addRdata() for \c TreeNodeRRset.
+ ///
+ /// It throws \c isc::Unexpected unconditionally.
+ virtual void addRdata(const std::string& rdata_str);
+
virtual dns::RdataIteratorPtr getRdataIterator() const;
/// \brief Specialized version of \c getRRsig() for \c TreeNodeRRset.
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index e796dd4fcd..ddf6a1974a 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -69,7 +69,7 @@ typedef boost::function<void(isc::dns::ConstRRsetPtr)> LoadCallback;
class ZoneDataLoader : boost::noncopyable {
public:
ZoneDataLoader(util::MemorySegment& mem_sgmt,
- const isc::dns::RRClass rrclass,
+ const isc::dns::RRClass& rrclass,
const isc::dns::Name& zone_name, ZoneData& zone_data) :
updater_(mem_sgmt, rrclass, zone_name, zone_data)
{}
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index e8826bdd5c..5a45be43f9 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -67,7 +67,7 @@ public:
/// on the given memory segment. Currently, at most one zone data
/// updater may exist on the same memory segment.
ZoneDataUpdater(util::MemorySegment& mem_sgmt,
- isc::dns::RRClass rrclass,
+ const isc::dns::RRClass& rrclass,
const isc::dns::Name& zone_name,
ZoneData& zone_data) :
mem_sgmt_(mem_sgmt),
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index 6bad5160e4..dd1c721052 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -146,8 +146,6 @@ public:
///
/// \param mem_sgmt The \c MemorySegment that allocated memory for
/// \c ztable and used for prior calls to \c addZone().
- /// \param zone_class The RR class of the zone. It must be the RR class
- /// that is supposed to be associated to the zone table.
/// \param ztable A non NULL pointer to a valid \c ZoneTable object
/// that was originally created by the \c create() method (the behavior
/// is undefined if this condition isn't met).
diff --git a/src/lib/datasrc/memory/zone_writer_local.h b/src/lib/datasrc/memory/zone_writer_local.h
new file mode 100644
index 0000000000..e387433368
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_writer_local.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MEM_ZONE_WRITER_LOCAL_H
+#define MEM_ZONE_WRITER_LOCAL_H
+
+#include "zone_writer.h"
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+class ZoneData;
+class ZoneTableSegmentLocal;
+
+/// \brief Writer implementation which loads data locally.
+///
+/// This implementation prepares a clean zone data and lets one callback
+/// to fill it and another to install it somewhere. The class does mostly
+/// nothing (and delegates the work to the callbacks), just stores little bit
+/// of state between the calls.
+class ZoneWriterLocal : public ZoneWriter {
+public:
+ /// \brief Constructor
+ ///
+ /// \param segment The zone table segment to store the zone into.
+ /// \param load_action The callback used to load data.
+ /// \param origin The origin of the zone.
+ /// \param rrclass The class of the zone.
+ ZoneWriterLocal(ZoneTableSegmentLocal* segment,
+ const LoadAction& load_action, const dns::Name& origin,
+ const dns::RRClass& rrclass);
+
+ /// \brief Destructor
+ ~ZoneWriterLocal();
+
+ /// \brief Loads the data.
+ ///
+ /// This calls the load_action (passed to constructor) and stores the
+ /// data for future use.
+ ///
+ /// \throw isc::InvalidOperation if it is called the second time in
+ /// lifetime of the object.
+ /// \throw Whatever the load_action throws, it is propagated up.
+ virtual void load();
+
+ /// \brief Installs the zone.
+ ///
+ /// It modifies the zone table accessible through the segment (passed to
+ /// constructor).
+ ///
+ /// \throw isc::InvalidOperation if it is called the second time in
+ /// lifetime of the object or if load() was not called previously or if
+ /// cleanup() was already called.
+ virtual void install();
+
+ /// \brief Clean up memory.
+ ///
+ /// Cleans up the memory used by load()ed zone if not yet installed, or
+ /// the old zone replaced by install().
+ virtual void cleanup();
+private:
+ ZoneTableSegmentLocal* segment_;
+ LoadAction load_action_;
+ dns::Name origin_;
+ dns::RRClass rrclass_;
+ ZoneData* zone_data_;
+ enum State {
+ ZW_UNUSED,
+ ZW_LOADED,
+ ZW_INSTALLED,
+ ZW_CLEANED
+ };
+ State state_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 8fec5c3bd7..02b55e9c57 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -904,6 +904,7 @@ public:
/// sequence. Note that because of serial number rollover, it may well
/// be that the start serial number is greater than the end one.
///
+ /// \param accessor The accessor to the database to use to get data.
/// \param zone_id ID of the zone (in the zone table)
/// \param start Serial number of first version in difference sequence
/// \param end Serial number of last version in difference sequence
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index 7de5cda2a9..2fff10fb51 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -142,7 +142,7 @@ public:
" \"noiter.org\", \"null.org\"]"
"}]"))
{
- for (size_t i(0); i < ds_count; ++ i) {
+ for (size_t i = 0; i < ds_count; ++ i) {
boost::shared_ptr<MockDataSourceClient>
ds(new MockDataSourceClient(ds_zones[i]));
ds_.push_back(ds);
@@ -155,12 +155,12 @@ public:
~ListTest() {
ds_info_.clear();
- for (size_t i(0); i < ds_count; ++ i) {
+ for (size_t i = 0; i < ds_count; ++ i) {
ds_[i].reset();
}
ds_.clear();
- for (size_t i(0); i < ds_count; ++ i) {
+ for (size_t i = 0; i < ds_count; ++ i) {
boost::interprocess::file_mapping::remove(
getMappedFilename(i).c_str());
}
@@ -465,7 +465,7 @@ const char* const test_names[] = {
TEST_P(ListTest, multiExactMatch) {
// Run through all the multi-configurations
- for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
+ for (size_t i = 0; i < sizeof(test_names) / sizeof(*test_names); ++i) {
SCOPED_TRACE(test_names[i]);
multiConfiguration(i);
// Something that is nowhere there
@@ -484,7 +484,7 @@ TEST_P(ListTest, multiExactMatch) {
TEST_P(ListTest, multiBestMatch) {
// Run through all the multi-configurations
- for (size_t i(0); i < 4; ++ i) {
+ for (size_t i = 0; i < 4; ++ i) {
SCOPED_TRACE(test_names[i]);
multiConfiguration(i);
// Something that is nowhere there
@@ -545,7 +545,7 @@ TEST_P(ListTest, configureParams) {
"{}",
NULL
};
- for (const char** param(params); *param; ++param) {
+ for (const char** param = params; *param; ++param) {
SCOPED_TRACE(*param);
ConstElementPtr elem(Element::fromJSON(string("["
"{"
@@ -672,7 +672,7 @@ TEST_P(ListTest, wrongConfig) {
// Put something inside to see it survives the exception
list_->configure(config_elem_, true);
checkDS(0, "test_type", "{}", false);
- for (const char** config(configs); *config; ++config) {
+ for (const char** config = configs; *config; ++config) {
SCOPED_TRACE(*config);
ConstElementPtr elem(Element::fromJSON(*config));
EXPECT_THROW(list_->configure(elem, true),
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index e6a27b465b..17bf7ba4c7 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -293,7 +293,7 @@ private:
if (position_ == domain_.end()) {
return (false);
} else {
- for (size_t i(0); i < COLUMN_COUNT; ++ i) {
+ for (size_t i = 0; i < COLUMN_COUNT; ++i) {
columns[i] = (*position_)[i];
}
++ position_;
@@ -1683,7 +1683,7 @@ findTestCommon(ZoneFinder& finder, const isc::dns::Name& name,
const isc::dns::RRType& type,
ConstZoneFinderContextPtr actual_result,
const isc::dns::RRType& expected_type,
- const isc::dns::RRTTL expected_ttl,
+ const isc::dns::RRTTL& expected_ttl,
ZoneFinder::Result expected_result,
const std::vector<string>& expected_rdatas,
const std::vector<string>& expected_sig_rdatas,
@@ -1737,7 +1737,7 @@ doFindTest(ZoneFinder& finder,
const isc::dns::Name& name,
const isc::dns::RRType& type,
const isc::dns::RRType& expected_type,
- const isc::dns::RRTTL expected_ttl,
+ const isc::dns::RRTTL& expected_ttl,
ZoneFinder::Result expected_result,
const std::vector<std::string>& expected_rdatas,
const std::vector<std::string>& expected_sig_rdatas,
@@ -1758,7 +1758,7 @@ doFindAtOriginTest(ZoneFinder& finder,
const isc::dns::Name& origin,
const isc::dns::RRType& type,
const isc::dns::RRType& expected_type,
- const isc::dns::RRTTL expected_ttl,
+ const isc::dns::RRTTL& expected_ttl,
ZoneFinder::Result expected_result,
const std::vector<std::string>& expected_rdatas,
const std::vector<std::string>& expected_sig_rdatas,
@@ -1781,7 +1781,7 @@ doFindAtOriginTest(ZoneFinder& finder,
void
doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
ZoneFinder::Result expected_result,
- const isc::dns::RRType expected_type,
+ const isc::dns::RRType& expected_type,
std::vector<std::string> expected_rdata,
const isc::dns::Name& expected_name =
isc::dns::Name::ROOT_NAME(),
@@ -1814,7 +1814,7 @@ doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
std::sort(rdata.begin(), rdata.end());
std::sort(expected_rdata.begin(), expected_rdata.end());
ASSERT_EQ(expected_rdata.size(), rdata.size());
- for (size_t i(0); i < expected_rdata.size(); ++ i) {
+ for (size_t i = 0; i < expected_rdata.size(); ++i) {
EXPECT_EQ(expected_rdata[i], rdata[i]);
}
EXPECT_TRUE(expected_rdata == rdata);
@@ -3023,7 +3023,7 @@ TEST_P(DatabaseClientTest, findRRSIGsWithoutDNSSEC) {
std::sort(rdata.begin(), rdata.end());
std::sort(expected_rdata.begin(), expected_rdata.end());
ASSERT_EQ(expected_rdata.size(), rdata.size());
- for (size_t i(0); i < expected_rdata.size(); ++ i) {
+ for (size_t i = 0; i < expected_rdata.size(); ++i) {
EXPECT_EQ(expected_rdata[i], rdata[i]);
}
}
@@ -3065,7 +3065,7 @@ TEST_P(DatabaseClientTest, getAll) {
EXPECT_EQ(RRType::A(), target[a_idx]->getType());
std::string previous;
size_t count(0);
- for (RdataIteratorPtr it(target[a_idx]->getRdataIterator());
+ for (RdataIteratorPtr it = target[a_idx]->getRdataIterator();
!it->isLast(); it->next()) {
++count;
EXPECT_NE(previous, it->getCurrent().toText());
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
index fb4487ad48..6e96243e93 100644
--- a/src/lib/datasrc/tests/master_loader_callbacks_test.cc
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -52,7 +52,7 @@ public:
rrset.getTTL()));
EXPECT_FALSE(rrset.getRRsig()) << "Unexpected RRSIG on rrset, not "
"copying. Following check will likely fail as a result.";
- for (isc::dns::RdataIteratorPtr it(rrset.getRdataIterator());
+ for (isc::dns::RdataIteratorPtr it = rrset.getRdataIterator();
!it->isLast(); it->next()) {
copy->addRdata(it->getCurrent());
}
diff --git a/src/lib/datasrc/tests/memory/domaintree_unittest.cc b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
index 7568f8dfab..8648dc87a1 100644
--- a/src/lib/datasrc/tests/memory/domaintree_unittest.cc
+++ b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
@@ -1253,7 +1253,7 @@ previousWalk(TestDomainTree& dtree, const TestDomainTreeNode* node,
EXPECT_EQ(static_cast<void*>(NULL), node);
node = dtree.previousNode(node_path);
}
- for (size_t i(chain_length); i > 0; --i) {
+ for (size_t i = chain_length; i > 0; --i) {
EXPECT_NE(static_cast<void*>(NULL), node);
EXPECT_EQ(Name(ordered_names[i - 1]), node_path.getAbsoluteName());
// Find the node at the path and check the value is the same
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 73862e3a8c..7644a1e8cf 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -774,13 +774,22 @@ TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
EXPECT_TRUE(seen_nsec3);
}
-TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
+TEST_F(MemoryClientTest, getIteratorGetSOA) {
loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
- // This method is not implemented.
- EXPECT_THROW(iterator->getSOA(), isc::NotImplemented);
+ ConstRRsetPtr soa_rrset(iterator->getSOA());
+
+ ASSERT_EQ(RRType::SOA(), soa_rrset->getType());
+ ASSERT_EQ(1, soa_rrset->getRdataCount());
+
+ RdataIteratorPtr it(soa_rrset->getRdataIterator());
+ const generic::SOA& soa
+ (dynamic_cast<const generic::SOA&>(it->getCurrent()));
+
+ EXPECT_EQ(71, soa.getSerial().getValue());
+ EXPECT_EQ(3600, soa.getMinimum());
}
TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
diff --git a/src/lib/datasrc/tests/memory/treenode_rrset_unittest.cc b/src/lib/datasrc/tests/memory/treenode_rrset_unittest.cc
index 4d1f6e1201..a1f1b28174 100644
--- a/src/lib/datasrc/tests/memory/treenode_rrset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/treenode_rrset_unittest.cc
@@ -333,6 +333,37 @@ checkToWireResult(OutputType& expected_output, OutputType& actual_output,
actual_output.getData(), actual_output.getLength());
}
+TEST_F(TreeNodeRRsetTest, getLength) {
+ // A RR
+ // www.example.com = 1 + 3 + 1 + 7 + 1 + 3 + 1 = 17 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // A RDATA = 4 octets
+ // Total = 17 + 2 + 2 + 4 + 2 + 4 = 31 octets
+
+ // RRSIG RR
+ // www.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 17 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // RRSIG RDATA = 18 + [1 + 7 + 1 + 3 + 1 (example.com)] + 3 (base64
+ // decode of FAKE) octets
+ // Total = 17 + 2 + 2 + 4 + 2 + 34 = 61 octets
+
+ // 1. with RRSIG, DNSSEC not OK
+ // ` 2 A RRs + 0 RRSIG RRs
+ const TreeNodeRRset rrset1(rrclass_, www_node_, a_rdataset_, false);
+ EXPECT_EQ(31 + 31, rrset1.getLength());
+
+ // 2. with RRSIG, DNSSEC OK
+ // ` 2 A RRs + 1 RRSIG RR
+ const TreeNodeRRset rrset2(rrclass_, www_node_, a_rdataset_, true);
+ EXPECT_EQ(31 + 31 + 61, rrset2.getLength());
+}
+
TEST_F(TreeNodeRRsetTest, toWire) {
MessageRenderer expected_renderer, actual_renderer;
OutputBuffer expected_buffer(0), actual_buffer(0);
@@ -663,6 +694,7 @@ TEST_F(TreeNodeRRsetTest, unexpectedMethods) {
EXPECT_THROW(rrset.setTTL(RRTTL(0)), isc::Unexpected);
EXPECT_THROW(rrset.addRdata(createRdata(RRType::A(), rrclass_, "0.0.0.0")),
isc::Unexpected);
+ EXPECT_THROW(rrset.addRdata("0.0.0.0"), isc::Unexpected);
RdataPtr sig_rdata = createRdata(
RRType::RRSIG(), rrclass_,
"A 5 2 3600 20120814220826 20120715220826 5300 example.com. FAKE");
diff --git a/src/lib/datasrc/tests/mock_client.cc b/src/lib/datasrc/tests/mock_client.cc
index 8cdc05dacb..1909496f13 100644
--- a/src/lib/datasrc/tests/mock_client.cc
+++ b/src/lib/datasrc/tests/mock_client.cc
@@ -128,7 +128,7 @@ private:
MockDataSourceClient::MockDataSourceClient(const char* zone_names[]) :
have_a_(true), use_baditerator_(true)
{
- for (const char** zone(zone_names); *zone; ++zone) {
+ for (const char** zone = zone_names; *zone; ++zone) {
zones.insert(Name(*zone));
}
}
@@ -145,7 +145,7 @@ MockDataSourceClient::MockDataSourceClient(
EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
"and it never should be created as a data source client";
if (configuration_->getType() == data::Element::list) {
- for (size_t i(0); i < configuration_->size(); ++i) {
+ for (size_t i = 0; i < configuration_->size(); ++i) {
zones.insert(Name(configuration_->get(i)->stringValue()));
}
}
diff --git a/src/lib/datasrc/zone_finder.cc b/src/lib/datasrc/zone_finder.cc
index 70cb8bf876..1fcdf2de6b 100644
--- a/src/lib/datasrc/zone_finder.cc
+++ b/src/lib/datasrc/zone_finder.cc
@@ -130,7 +130,7 @@ ZoneFinder::stripRRsigs(isc::dns::ConstRRsetPtr rp,
rp->getClass(),
rp->getType(),
rp->getTTL()));
- for (isc::dns::RdataIteratorPtr i(rp->getRdataIterator());
+ for (isc::dns::RdataIteratorPtr i = rp->getRdataIterator();
!i->isLast();
i->next()) {
result_base->addRdata(i->getCurrent());
diff --git a/src/lib/datasrc/zone_finder.h b/src/lib/datasrc/zone_finder.h
index 83851f6df0..13fccc553f 100644
--- a/src/lib/datasrc/zone_finder.h
+++ b/src/lib/datasrc/zone_finder.h
@@ -648,10 +648,10 @@ public:
/// proofs, wildcard information etc. The options parameter works the
/// same way and it should conform to the same exception restrictions.
///
- /// \param name \see find, parameter name
+ /// \param name see find(), parameter name
/// \param target the successfull result is returned through this
- /// \param options \see find, parameter options
- /// \return \see find and it's result
+ /// \param options see find(), parameter options
+ /// \return see find() and its result
virtual boost::shared_ptr<Context> findAll(
const isc::dns::Name& name,
std::vector<isc::dns::ConstRRsetPtr> &target,
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 17489278ee..640bac1b88 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -19,6 +19,7 @@ libb10_dhcp___la_SOURCES += duid.cc duid.h
libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
+libb10_dhcp___la_SOURCES += iface_mgr_error_handler.h
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h
index 19bdaa0cb9..6031f8d611 100644
--- a/src/lib/dhcp/docsis3_option_defs.h
+++ b/src/lib/dhcp/docsis3_option_defs.h
@@ -18,8 +18,8 @@
#include <dhcp/std_option_defs.h>
#include <dhcp/option_data_types.h>
-
-namespace {
+namespace isc {
+namespace dhcp {
#define VENDOR_ID_CABLE_LABS 4491
@@ -61,6 +61,11 @@ const OptionDefParams DOCSIS3_V6_DEFS[] = {
/// Number of option definitions defined.
const int DOCSIS3_V6_DEFS_SIZE = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
-}; // anonymous namespace
+/// The class as specified in vendor-class option by the devices
+extern const char* DOCSIS3_CLASS_EROUTER;
+extern const char* DOCSIS3_CLASS_MODEM;
+
+}; // isc::dhcp namespace
+}; // isc namespace
#endif // DOCSIS3_OPTION_DEFS_H
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index d97614bae1..a5311343e7 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
#include <dhcp/pkt_filter_inet.h>
#include <dhcp/pkt_filter_inet6.h>
#include <exceptions/exceptions.h>
@@ -37,40 +38,6 @@
#include <string.h>
#include <sys/select.h>
-/// @brief A macro which handles an error in IfaceMgr.
-///
-/// There are certain cases when IfaceMgr may hit an error which shouldn't
-/// result in interruption of the function processing. A typical case is
-/// the function which opens sockets on available interfaces for a DHCP
-/// server. If this function fails to open a socket on a specific interface
-/// (for example, there is another socket already open on this interface
-/// and bound to the same address and port), it is desired that the server
-/// logs a warning but will try to open sockets on other interfaces. In order
-/// to log an error, the IfaceMgr will use the error handler function provided
-/// by the server and pass an error string to it. When the handler function
-/// returns, the IfaceMgr will proceed to open other sockets. It is allowed
-/// that the error handler function is not installed (is NULL). In these
-/// cases it is expected that the exception is thrown instead. A possible
-/// solution would be to enclose this conditional behavior in a function.
-/// However, despite the hate for macros, the macro seems to be a bit
-/// better solution in this case as it allows to convenietly pass an
-/// error string in a stream (not as a string).
-///
-/// @param ex_type Exception to be thrown if error_handler is NULL.
-/// @param handler Error handler function to be called or NULL to indicate
-/// that exception should be thrown instead.
-/// @param stream stream object holding an error string.
-#define IFACEMGR_ERROR(ex_type, handler, stream) \
-{ \
- std::ostringstream oss__; \
- oss__ << stream; \
- if (handler) { \
- handler(oss__.str()); \
- } else { \
- isc_throw(ex_type, oss__); \
- } \
-} \
-
using namespace std;
using namespace isc::asiolink;
using namespace isc::util::io::internal;
@@ -183,7 +150,7 @@ bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
return (false);
}
-bool Iface::delSocket(uint16_t sockfd) {
+bool Iface::delSocket(const uint16_t sockfd) {
list<SocketInfo>::iterator sock = sockets_.begin();
while (sock!=sockets_.end()) {
if (sock->sockfd_ == sockfd) {
@@ -203,7 +170,6 @@ bool Iface::delSocket(uint16_t sockfd) {
IfaceMgr::IfaceMgr()
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
control_buf_(new char[control_buf_len_]),
- session_socket_(INVALID_SOCKET), session_callback_(NULL),
packet_filter_(new PktFilterInet()),
packet_filter6_(new PktFilterInet6())
{
@@ -226,7 +192,7 @@ void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
i != unicasts_.end(); ++i) {
if (*i == addr) {
- isc_throw(BadValue, "Address " << addr.toText()
+ isc_throw(BadValue, "Address " << addr
<< " already defined on the " << name_ << " interface.");
}
}
@@ -261,6 +227,37 @@ IfaceMgr::isDirectResponseSupported() const {
}
void
+IfaceMgr::addExternalSocket(int socketfd, SocketCallback callback) {
+ for (SocketCallbackInfoContainer::iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+
+ // There's such a socket description there already.
+ // Update the callback and we're done
+ if (s->socket_ == socketfd) {
+ s->callback_ = callback;
+ return;
+ }
+ }
+
+ // Add a new entry to the callbacks vector
+ SocketCallbackInfo x;
+ x.socket_ = socketfd;
+ x.callback_ = callback;
+ callbacks_.push_back(x);
+}
+
+void
+IfaceMgr::deleteExternalSocket(int socketfd) {
+ for (SocketCallbackInfoContainer::iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+ if (s->socket_ == socketfd) {
+ callbacks_.erase(s);
+ return;
+ }
+ }
+}
+
+void
IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) {
// Do not allow NULL pointer.
if (!packet_filter) {
@@ -321,6 +318,24 @@ IfaceMgr::hasOpenSocket(const uint16_t family) const {
return (false);
}
+bool
+IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
+ // Iterate over all interfaces and search for open sockets.
+ for (IfaceCollection::const_iterator iface = ifaces_.begin();
+ iface != ifaces_.end(); ++iface) {
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ // Check if the socket address matches the specified address.
+ if (sock->addr_ == addr) {
+ return (true);
+ }
+ }
+ }
+ // There are no open sockets found for the specified family.
+ return (false);
+}
+
void IfaceMgr::stubDetectIfaces() {
string ifaceName;
const string v4addr("127.0.0.1"), v6addr("::1");
@@ -481,7 +496,6 @@ IfaceMgr::openSockets6(const uint16_t port,
try {
openSocket(iface->getName(), *addr, port);
-
} catch (const Exception& ex) {
IFACEMGR_ERROR(SocketConfigError, error_handler,
"Failed to open unicast socket on interface "
@@ -509,64 +523,17 @@ IfaceMgr::openSockets6(const uint16_t port,
// with interface with 2 global addresses, we would bind 3 sockets
// (one for link-local and two for global). That would result in
// getting each message 3 times.
- if (!addr->getAddress().to_v6().is_link_local()){
+ if (!addr->isV6LinkLocal()){
continue;
}
- // Open socket and join multicast group only if the interface
- // is multicast-capable.
- // @todo The DHCPv6 requires multicast so we may want to think
- // whether we want to open the socket on a multicast-incapable
- // interface or not. For now, we prefer to be liberal and allow
- // it for some odd use cases which may utilize non-multicast
- // interfaces. Perhaps a warning should be emitted if the
- // interface is not a multicast one.
-
- // The sock variable will hold a socket descriptor. It may be
- // used to close a socket if the function fails to bind to
- // multicast address on Linux systems. Because we only bind
- // a socket to multicast address on Linux, on other systems
- // the sock variable will be initialized but unused. We have
- // to suppress the cppcheck warning which shows up on non-Linux
- // systems.
- // cppcheck-suppress variableScope
- int sock;
- try {
- // cppcheck-suppress unreadVariable
- sock = openSocket(iface->getName(), *addr, port,
- iface->flag_multicast_);
-
- } catch (const Exception& ex) {
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "Failed to open link-local socket on "
- " interface " << iface->getName() << ": "
- << ex.what());
- continue;
-
+ // Run OS-specific function to open a socket on link-local address
+ // and join multicast group (non-Linux OSes), or open two sockets and
+ // bind one to link-local, another one to multicast address.
+ if (openMulticastSocket(*iface, *addr, port, error_handler)) {
+ ++count;
}
- count++;
-
- /// @todo: Remove this ifdef once we start supporting BSD systems.
-#if defined(OS_LINUX)
- // To receive multicast traffic, Linux requires binding socket to
- // a multicast group. That in turn doesn't work on NetBSD.
- if (iface->flag_multicast_) {
- try {
- openSocket(iface->getName(),
- IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
- port);
- } catch (const Exception& ex) {
- // Delete previously opened socket.
- iface->delSocket(sock);
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "Failed to open multicast socket on"
- " interface " << iface->getName()
- << ", reason: " << ex.what());
- continue;
- }
- }
-#endif
}
}
return (count > 0);
@@ -644,7 +611,7 @@ int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
} else {
isc_throw(BadValue, "Failed to detect family of address: "
- << addr.toText());
+ << addr);
}
}
@@ -719,14 +686,14 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
}
// If we got here it means that we did not find specified address
// on any available interface.
- isc_throw(BadValue, "There is no such address " << addr.toText());
+ isc_throw(BadValue, "There is no such address " << addr);
}
int IfaceMgr::openSocketFromRemoteAddress(const IOAddress& remote_addr,
const uint16_t port) {
try {
// Get local address to be used to connect to remote location.
- IOAddress local_address(getLocalAddress(remote_addr, port).getAddress());
+ IOAddress local_address(getLocalAddress(remote_addr, port));
return openSocketFromAddress(local_address, port);
} catch (const Exception& e) {
isc_throw(SocketConfigError, e.what());
@@ -791,7 +758,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
return IOAddress(local_address);
}
-
int
IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
const bool join_multicast) {
@@ -853,7 +819,6 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
IfaceCollection::const_iterator iface;
fd_set sockets;
int maxfd = 0;
- stringstream names;
FD_ZERO(&sockets);
@@ -868,7 +833,6 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Only deal with IPv4 addresses.
if (s->addr_.isV4()) {
- names << s->sockfd_ << "(" << iface->getName() << ") ";
// Add this socket to listening set
FD_SET(s->sockfd_, &sockets);
@@ -879,13 +843,15 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
}
}
- // if there is session socket registered...
- if (session_socket_ != INVALID_SOCKET) {
- // at it to the set as well
- FD_SET(session_socket_, &sockets);
- if (maxfd < session_socket_)
- maxfd = session_socket_;
- names << session_socket_ << "(session)";
+ // if there are any callbacks for external sockets registered...
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfoContainer::const_iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+ FD_SET(s->socket_, &sockets);
+ if (maxfd < s->socket_) {
+ maxfd = s->socket_;
+ }
+ }
}
struct timeval select_timeout;
@@ -902,18 +868,22 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
}
// Let's find out which socket has the data
- if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) {
- // something received over session socket
- if (session_callback_) {
- // in theory we could call io_service.run_one() here, instead of
- // implementing callback mechanism, but that would introduce
- // asiolink dependency to libdhcp++ and that is something we want
- // to avoid (see CPE market and out long term plans for minimalistic
- // implementations.
- session_callback_();
+ for (SocketCallbackInfoContainer::iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+ if (!FD_ISSET(s->socket_, &sockets)) {
+ continue;
}
- return (Pkt4Ptr()); // NULL
+ // something received over external socket
+
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ if (s->callback_) {
+ s->callback_();
+ }
+
+ return (Pkt4Ptr());
}
// Let's find out which interface/socket has the data
@@ -950,7 +920,6 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
const SocketInfo* candidate = 0;
fd_set sockets;
int maxfd = 0;
- stringstream names;
FD_ZERO(&sockets);
@@ -965,7 +934,6 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
// Only deal with IPv6 addresses.
if (s->addr_.isV6()) {
- names << s->sockfd_ << "(" << iface->getName() << ") ";
// Add this socket to listening set
FD_SET(s->sockfd_, &sockets);
@@ -976,13 +944,17 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
}
}
- // if there is session socket registered...
- if (session_socket_ != INVALID_SOCKET) {
- // at it to the set as well
- FD_SET(session_socket_, &sockets);
- if (maxfd < session_socket_)
- maxfd = session_socket_;
- names << session_socket_ << "(session)";
+ // if there are any callbacks for external sockets registered...
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfoContainer::const_iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+
+ // Add it to the set as well
+ FD_SET(s->socket_, &sockets);
+ if (maxfd < s->socket_) {
+ maxfd = s->socket_;
+ }
+ }
}
struct timeval select_timeout;
@@ -999,18 +971,22 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
}
// Let's find out which socket has the data
- if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) {
- // something received over session socket
- if (session_callback_) {
- // in theory we could call io_service.run_one() here, instead of
- // implementing callback mechanism, but that would introduce
- // asiolink dependency to libdhcp++ and that is something we want
- // to avoid (see CPE market and out long term plans for minimalistic
- // implementations.
- session_callback_();
+ for (SocketCallbackInfoContainer::iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+ if (!FD_ISSET(s->socket_, &sockets)) {
+ continue;
}
- return (Pkt6Ptr()); // NULL
+ // something received over external socket
+
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ if (s->callback_) {
+ s->callback_();
+ }
+
+ return (Pkt6Ptr());
}
// Let's find out which interface/socket has the data
@@ -1057,7 +1033,7 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
}
// Sockets bound to multicast address are useless for sending anything.
- if (s->addr_.getAddress().to_v6().is_multicast()) {
+ if (s->addr_.isV6Multicast()) {
continue;
}
@@ -1074,10 +1050,10 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
// If we want to send something to link-local and the socket is
// bound to link-local or we want to send to global and the socket
// is bound to global, then use it as candidate
- if ( (pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
- s->addr_.getAddress().to_v6().is_link_local()) ||
- (!pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
- !s->addr_.getAddress().to_v6().is_link_local()) ) {
+ if ( (pkt.getRemoteAddr().isV6LinkLocal() &&
+ s->addr_.isV6LinkLocal()) ||
+ (!pkt.getRemoteAddr().isV6LinkLocal() &&
+ !s->addr_.isV6LinkLocal()) ) {
candidate = s;
}
}
@@ -1113,6 +1089,5 @@ IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
<< " does not have any suitable IPv4 sockets open.");
}
-
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 83b19dc2f2..12212c74c8 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -34,6 +34,7 @@ namespace isc {
namespace dhcp {
+
/// @brief IfaceMgr exception thrown thrown when interface detection fails.
class IfaceDetectError : public Exception {
public:
@@ -393,8 +394,20 @@ boost::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
///
class IfaceMgr : public boost::noncopyable {
public:
- /// Defines callback used when commands are received over control session.
- typedef void (*SessionCallback) (void);
+ /// Defines callback used when data is received over external sockets.
+ typedef boost::function<void ()> SocketCallback;
+
+ /// Keeps callback information for external sockets.
+ struct SocketCallbackInfo {
+ /// Socket descriptor of the external socket.
+ int socket_;
+
+ /// A callback that will be called when data arrives over socket_.
+ SocketCallback callback_;
+ };
+
+ /// Defines storage container for callbacks for external sockets
+ typedef std::list<SocketCallbackInfo> SocketCallbackInfoContainer;
/// @brief Packet reception buffer size
///
@@ -784,17 +797,18 @@ public:
/// @return number of detected interfaces
uint16_t countIfaces() { return ifaces_.size(); }
- /// @brief Sets session socket and a callback
+ /// @brief Adds external socket and a callback
///
- /// Specifies session socket and a callback that will be called
+ /// Specifies external socket and a callback that will be called
/// when data will be received over that socket.
///
/// @param socketfd socket descriptor
/// @param callback callback function
- void set_session_socket(int socketfd, SessionCallback callback) {
- session_socket_ = socketfd;
- session_callback_ = callback;
- }
+ void addExternalSocket(int socketfd, SocketCallback callback);
+
+ /// @brief Deletes external socket
+
+ void deleteExternalSocket(int socketfd);
/// @brief Set packet filter object to handle sending and receiving DHCPv4
/// messages.
@@ -862,8 +876,23 @@ public:
ifaces_.push_back(iface);
}
- /// A value of socket descriptor representing "not specified" state.
- static const int INVALID_SOCKET = -1;
+ /// @brief Checks if there is at least one socket of the specified family
+ /// open.
+ ///
+ /// @param family A socket family.
+ ///
+ /// @return true if there is at least one socket open, false otherwise.
+ bool hasOpenSocket(const uint16_t family) const;
+
+ /// @brief Checks if there is a socket open and bound to an address.
+ ///
+ /// This function checks if one of the sockets opened by the IfaceMgr is
+ /// bound to the IP address specified as the method parameter.
+ ///
+ /// @param addr Address of the socket being searched.
+ ///
+ /// @return true if there is a socket bound to the specified address.
+ bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const;
// don't use private, we need derived classes in tests
protected:
@@ -958,13 +987,7 @@ protected:
/// @return true if successful, false otherwise
bool os_receive4(struct msghdr& m, Pkt4Ptr& pkt);
- /// Socket descriptor of the session socket.
- int session_socket_;
-
- /// A callback that will be called when data arrives over session_socket_.
- SessionCallback session_callback_;
private:
-
/// @brief Identifies local network address to be used to
/// connect to remote address.
///
@@ -983,13 +1006,29 @@ private:
getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
const uint16_t port);
- /// @brief Checks if there is at least one socket of the specified family
- /// open.
+
+ /// @brief Open an IPv6 socket with multicast support.
///
- /// @param family A socket family.
+ /// This function opens socket(s) to allow reception of the DHCPv6 sent
+ /// to multicast address. It opens an IPv6 socket, binds it to link-local
+ /// address and joins multicast group (on non-Linux systems) or opens two
+ /// IPv6 sockets and binds one of them to link-local address and another
+ /// one to multicast address (on Linux systems).
///
- /// @return true if there is at least one socket open, false otherwise.
- bool hasOpenSocket(const uint16_t family) const;
+ /// @note This function is intended to be called internally by the
+ /// @c IfaceMgr::openSockets6. It is not intended to be called from any
+ /// other function.
+ ///
+ /// @param iface Interface on which socket should be open.
+ /// @param addr Link-local address to bind the socket to.
+ /// @param port Port number to bind socket to.
+ /// @param error_handler Error handler function to be called when an
+ /// error occurs during opening a socket, or NULL if exception should
+ /// be thrown upon error.
+ bool openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler = NULL);
/// Holds instance of a class derived from PktFilter, used by the
/// IfaceMgr to open sockets and send/receive packets through these
@@ -1006,6 +1045,9 @@ private:
/// messages. It is possible to supply a custom object using
/// setPacketFilter method.
PktFilter6Ptr packet_filter6_;
+
+ /// @brief Contains list of callbacks for external sockets
+ SocketCallbackInfoContainer callbacks_;
};
}; // namespace isc::dhcp
diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc
index be73bcfc2e..7a01228256 100644
--- a/src/lib/dhcp/iface_mgr_bsd.cc
+++ b/src/lib/dhcp/iface_mgr_bsd.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#if defined(OS_BSD)
#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
@@ -149,6 +150,28 @@ IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
setPacketFilter(PktFilterPtr(new PktFilterInet()));
}
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ try {
+ // This should open a socket, bound it to link-local address
+ // and join multicast group.
+ openSocket(iface.getName(), addr, port,
+ iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "Failed to open link-local socket on "
+ " interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+ return (true);
+}
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/iface_mgr_error_handler.h b/src/lib/dhcp/iface_mgr_error_handler.h
new file mode 100644
index 0000000000..c5ef5b401b
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr_error_handler.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef IFACE_MGR_ERROR_HANDLER_H
+#define IFACE_MGR_ERROR_HANDLER_H
+
+/// @brief A macro which handles an error in IfaceMgr.
+///
+/// There are certain cases when IfaceMgr may hit an error which shouldn't
+/// result in interruption of the function processing. A typical case is
+/// the function which opens sockets on available interfaces for a DHCP
+/// server. If this function fails to open a socket on a specific interface
+/// (for example, there is another socket already open on this interface
+/// and bound to the same address and port), it is desired that the server
+/// logs a warning but will try to open sockets on other interfaces. In order
+/// to log an error, the IfaceMgr will use the error handler function provided
+/// by the server and pass an error string to it. When the handler function
+/// returns, the IfaceMgr will proceed to open other sockets. It is allowed
+/// that the error handler function is not installed (is NULL). In these
+/// cases it is expected that the exception is thrown instead. A possible
+/// solution would be to enclose this conditional behavior in a function.
+/// However, despite the hate for macros, the macro seems to be a bit
+/// better solution in this case as it allows to convenietly pass an
+/// error string in a stream (not as a string).
+///
+/// @param ex_type Exception to be thrown if error_handler is NULL.
+/// @param handler Error handler function to be called or NULL to indicate
+/// that exception should be thrown instead.
+/// @param stream stream object holding an error string.
+#define IFACEMGR_ERROR(ex_type, handler, stream) \
+{ \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ if (handler) { \
+ handler(oss__.str()); \
+ } else { \
+ isc_throw(ex_type, oss__); \
+ } \
+} \
+
+#endif // IFACE_MGR_ERROR_HANDLER_H
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index dddeb52921..f4b0613dd8 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -33,6 +33,7 @@
#include <asiolink/io_address.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
#include <dhcp/pkt_filter_inet.h>
#include <dhcp/pkt_filter_lpf.h>
#include <exceptions/exceptions.h>
@@ -533,6 +534,60 @@ bool IfaceMgr::os_receive4(struct msghdr&, Pkt4Ptr&) {
return (true);
}
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ // This variable will hold a descriptor of the socket bound to
+ // link-local address. It may be required for us to close this
+ // socket if an attempt to open and bind a socket to multicast
+ // address fails.
+ int sock;
+ try {
+ sock = openSocket(iface.getName(), addr, port,
+ iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "Failed to open link-local socket on "
+ " interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+
+ // To receive multicast traffic, Linux requires binding socket to
+ // the multicast address.
+
+ /// @todo The DHCPv6 requires multicast so we may want to think
+ /// whether we want to open the socket on a multicast-incapable
+ /// interface or not. For now, we prefer to be liberal and allow
+ /// it for some odd use cases which may utilize non-multicast
+ /// interfaces. Perhaps a warning should be emitted if the
+ /// interface is not a multicast one.
+ if (iface.flag_multicast_) {
+ try {
+ openSocket(iface.getName(),
+ IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ port);
+ } catch (const Exception& ex) {
+ // An attempt to open and bind a socket to multicast addres
+ // has failed. We have to close the socket we previously
+ // bound to link-local address - this is everything or
+ // nothing strategy.
+ iface.delSocket(sock);
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "Failed to open multicast socket on"
+ " interface " << iface.getName()
+ << ", reason: " << ex.what());
+ return (false);
+ }
+ }
+ // Both sockets have opened successfully.
+ return (true);
+}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc
index fe2b0b3df2..a78de8f4a5 100644
--- a/src/lib/dhcp/iface_mgr_sun.cc
+++ b/src/lib/dhcp/iface_mgr_sun.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#if defined(OS_SUN)
#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
@@ -153,6 +154,28 @@ IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
setPacketFilter(PktFilterPtr(new PktFilterInet()));
}
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ try {
+ // This should open a socket, bound it to link-local address
+ // and join multicast group.
+ openSocket(iface.getName(), addr, port,
+ iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "Failed to open link-local socket on "
+ " interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+ return (true);
+}
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 4577c642dc..f55f6bc075 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -52,6 +52,14 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_;
VendorOptionDefContainers LibDHCP::vendor6_defs_;
+// Those two vendor classes are used for cable modems:
+
+/// DOCSIS3.0 compatible cable modem
+const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
+
+/// DOCSIS3.0 cable modem that has router built-in
+const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
+
// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
// definitions there
void initOptionSpace(OptionDefContainer& defs,
@@ -223,10 +231,10 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
// The buffer being read comprises a set of options, each starting with
// a two-byte type code and a two-byte length field.
while (offset + 4 <= length) {
- uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+ uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
offset += 2;
- uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+ uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
offset += 2;
if (offset + opt_len > length) {
@@ -405,10 +413,10 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
// The buffer being read comprises a set of options, each starting with
// a two-byte type code and a two-byte length field.
while (offset + 4 <= length) {
- uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+ uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
offset += 2;
- uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+ uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
offset += 2;
if (offset + opt_len > length) {
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index f5ab75ede0..24ddc06854 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -250,35 +250,28 @@ uint8_t Option::getUint8() {
}
uint16_t Option::getUint16() {
- if (data_.size() < sizeof(uint16_t) ) {
- isc_throw(OutOfRange, "Attempt to read uint16 from option " << type_
- << " that has size " << data_.size());
- }
-
- return ( readUint16(&data_[0]) );
+ // readUint16() checks and throws OutOfRange if data_ is too small.
+ return (readUint16(&data_[0], data_.size()));
}
uint32_t Option::getUint32() {
- if (data_.size() < sizeof(uint32_t) ) {
- isc_throw(OutOfRange, "Attempt to read uint32 from option " << type_
- << " that has size " << data_.size());
- }
- return ( readUint32(&data_[0]) );
+ // readUint32() checks and throws OutOfRange if data_ is too small.
+ return (readUint32(&data_[0], data_.size()));
}
void Option::setUint8(uint8_t value) {
- data_.resize(1);
- data_[0] = value;
+ data_.resize(sizeof(value));
+ data_[0] = value;
}
void Option::setUint16(uint16_t value) {
- data_.resize(2);
- writeUint16(value, &data_[0]);
+ data_.resize(sizeof(value));
+ writeUint16(value, &data_[0], data_.size());
}
void Option::setUint32(uint32_t value) {
- data_.resize(4);
- writeUint32(value, &data_[0]);
+ data_.resize(sizeof(value));
+ writeUint32(value, &data_[0], data_.size());
}
bool Option::equal(const OptionPtr& other) const {
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
index 436d07d0ff..223da16e1e 100644
--- a/src/lib/dhcp/option4_addrlst.cc
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -53,7 +53,7 @@ Option4AddrLst::Option4AddrLst(uint8_t type, OptionBufferConstIter first,
while (first != last) {
const uint8_t* ptr = &(*first);
- addAddress(IOAddress(readUint32(ptr)));
+ addAddress(IOAddress(readUint32(ptr, distance(first, last))));
first += V4ADDRESS_LEN;
}
}
diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc
index 7f93a50d33..d546ab2f02 100644
--- a/src/lib/dhcp/option4_client_fqdn.cc
+++ b/src/lib/dhcp/option4_client_fqdn.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -217,7 +217,6 @@ setDomainName(const std::string& domain_name,
} else {
try {
domain_name_.reset(new isc::dns::Name(name));
- domain_name_type_ = name_type;
} catch (const Exception& ex) {
isc_throw(InvalidOption4FqdnDomainName,
@@ -227,6 +226,8 @@ setDomainName(const std::string& domain_name,
}
}
+
+ domain_name_type_ = name_type;
}
void
diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc
index cb14070527..45c22625ee 100644
--- a/src/lib/dhcp/option6_addrlst.cc
+++ b/src/lib/dhcp/option6_addrlst.cc
@@ -102,9 +102,9 @@ std::string Option6AddrLst::toText(int indent /* =0 */) {
tmp << "type=" << type_ << " " << addrs_.size() << "addr(s): ";
- for (AddressContainer::const_iterator addr=addrs_.begin();
- addr!=addrs_.end(); ++addr) {
- tmp << addr->toText() << " ";
+ for (AddressContainer::const_iterator addr = addrs_.begin();
+ addr != addrs_.end(); ++addr) {
+ tmp << *addr << " ";
}
return tmp.str();
}
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
index 761acae1d1..d9eb0b230b 100644
--- a/src/lib/dhcp/option6_client_fqdn.cc
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -188,7 +188,6 @@ setDomainName(const std::string& domain_name,
} else {
try {
domain_name_.reset(new isc::dns::Name(name, true));
- domain_name_type_ = name_type;
} catch (const Exception& ex) {
isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
@@ -197,6 +196,8 @@ setDomainName(const std::string& domain_name,
}
}
+
+ domain_name_type_ = name_type;
}
void
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 825a5bf130..a5751ddf11 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -72,12 +72,12 @@ void Option6IA::unpack(OptionBufferConstIter begin,
if (distance(begin, end) < OPTION6_IA_LEN) {
isc_throw(OutOfRange, "Option " << type_ << " truncated");
}
- iaid_ = readUint32( &(*begin) );
+ iaid_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
- t1_ = readUint32( &(*begin) );
+ t1_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
- t2_ = readUint32( &(*begin) );
+ t2_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
unpackOptions(OptionBuffer(begin, end));
diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc
index 39efa61824..9e02be7876 100644
--- a/src/lib/dhcp/option6_iaaddr.cc
+++ b/src/lib/dhcp/option6_iaaddr.cc
@@ -37,7 +37,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
valid_(valid) {
setEncapsulatedSpace("dhcp6");
if (!addr.isV6()) {
- isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address");
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
}
}
@@ -57,8 +57,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
buf.writeUint16(len() - getHeaderLen());
if (!addr_.isV6()) {
- isc_throw(isc::BadValue, addr_.toText()
- << " is not an IPv6 address");
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
}
buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN);
@@ -79,10 +78,10 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
addr_ = IOAddress::fromBytes(AF_INET6, &(*begin));
begin += V6ADDRESS_LEN;
- preferred_ = readUint32( &(*begin) );
+ preferred_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
- valid_ = readUint32( &(*begin) );
+ valid_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
unpackOptions(OptionBuffer(begin, end));
@@ -93,7 +92,7 @@ std::string Option6IAAddr::toText(int indent /* =0 */) {
for (int i=0; i<indent; i++)
tmp << " ";
- tmp << "type=" << type_ << "(IAADDR) addr=" << addr_.toText()
+ tmp << "type=" << type_ << "(IAADDR) addr=" << addr_
<< ", preferred-lft=" << preferred_ << ", valid-lft="
<< valid_ << endl;
diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc
index a7776d7cfe..357942f8a5 100644
--- a/src/lib/dhcp/option6_iaprefix.cc
+++ b/src/lib/dhcp/option6_iaprefix.cc
@@ -51,7 +51,7 @@ Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator beg
void Option6IAPrefix::pack(isc::util::OutputBuffer& buf) {
if (!addr_.isV6()) {
- isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address");
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
}
buf.writeUint16(type_);
@@ -76,10 +76,10 @@ void Option6IAPrefix::unpack(OptionBuffer::const_iterator begin,
isc_throw(OutOfRange, "Option " << type_ << " truncated");
}
- preferred_ = readUint32( &(*begin) );
+ preferred_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
- valid_ = readUint32( &(*begin) );
+ valid_ = readUint32(&(*begin), distance(begin, end));
begin += sizeof(uint32_t);
prefix_len_ = *begin;
@@ -98,7 +98,7 @@ std::string Option6IAPrefix::toText(int indent /* =0 */) {
for (int i=0; i<indent; i++)
tmp << " ";
- tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_.toText() << "/"
+ tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_ << "/"
<< prefix_len_ << ", preferred-lft=" << preferred_ << ", valid-lft="
<< valid_ << endl;
diff --git a/src/lib/dhcp/option6_iaprefix.h b/src/lib/dhcp/option6_iaprefix.h
index 0a790beddc..63ab9f735c 100644
--- a/src/lib/dhcp/option6_iaprefix.h
+++ b/src/lib/dhcp/option6_iaprefix.h
@@ -90,7 +90,8 @@ public:
/// sets address in this option.
///
- /// @param addr address to be sent in this option
+ /// @param prefix prefix to be sent in this option
+ /// @param length prefix length
void setPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t length) { addr_ = prefix; prefix_len_ = length; }
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 278c0c9bb7..0709d20110 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -54,7 +54,7 @@ OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
(address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
isc_throw(BadDataTypeCast, "invalid address specified "
- << address.toText() << ". Expected a valid IPv"
+ << address << ". Expected a valid IPv"
<< (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ?
"4" : "6") << " address.");
}
@@ -375,7 +375,7 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
break;
case OPT_IPV4_ADDRESS_TYPE:
case OPT_IPV6_ADDRESS_TYPE:
- text << readAddress(index).toText();
+ text << readAddress(index);
break;
case OPT_FQDN_TYPE:
text << readFqdn(index);
@@ -443,7 +443,7 @@ OptionCustom::writeAddress(const asiolink::IOAddress& address,
if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
(address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) {
isc_throw(BadDataTypeCast, "invalid address specified "
- << address.toText() << ". Expected a valid IPv"
+ << address << ". Expected a valid IPv"
<< (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
<< " address.");
}
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index d9d8a52379..fb4d101a15 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -298,12 +298,12 @@ public:
case 2:
// Calling readUint16 works either for unsigned
// or signed types.
- value = isc::util::readUint16(&(*buf.begin()));
+ value = isc::util::readUint16(&(*buf.begin()), buf.size());
break;
case 4:
// Calling readUint32 works either for unsigned
// or signed types.
- value = isc::util::readUint32(&(*buf.begin()));
+ value = isc::util::readUint32(&(*buf.begin()), buf.size());
break;
default:
// This should not happen because we made checks on data types
@@ -331,11 +331,11 @@ public:
break;
case 2:
buf.resize(buf.size() + 2);
- isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]);
+ isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2], 2);
break;
case 4:
buf.resize(buf.size() + 4);
- isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]);
+ isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4], 4);
break;
default:
// The cases above cover whole range of possible data lengths because
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index be1a5e7890..98765e6313 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -399,18 +399,49 @@ OptionDefinition::haveVendor6Format() const {
return (getType() == OPT_UINT32_TYPE && !getEncapsulatedSpace().empty());
}
+bool
+OptionDefinition::convertToBool(const std::string& value_str) const {
+ // Case insensitve check that the input is one of: "true" or "false".
+ if (boost::iequals(value_str, "true")) {
+ return (true);
+
+ } else if (boost::iequals(value_str, "false")) {
+ return (false);
+
+ }
+
+ // The input string is neither "true" nor "false", so let's check
+ // if it is not an integer wrapped in a string.
+ int result;
+ try {
+ result = boost::lexical_cast<int>(value_str);
+
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(BadDataTypeCast, "unable to covert the value '"
+ << value_str << "' to boolean data type");
+ }
+ // The boolean value is encoded in DHCP option as 0 or 1. Therefore,
+ // we only allow a user to specify those values for options which
+ // have boolean fields.
+ if (result != 1 && result != 0) {
+ isc_throw(BadDataTypeCast, "unable to convert '" << value_str
+ << "' to boolean data type");
+ }
+ return (static_cast<bool>(result));
+}
+
template<typename T>
T
OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
const {
- // Lexical cast in case of our data types make sense only
- // for uintX_t, intX_t and bool type.
- if (!OptionDataTypeTraits<T>::integer_type &&
- OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
+ // The lexical cast should be attempted when converting to an integer
+ // value only.
+ if (!OptionDataTypeTraits<T>::integer_type) {
isc_throw(BadDataTypeCast,
- "unable to do lexical cast to non-integer and"
- << " non-boolean data type");
+ "must not convert '" << value_str
+ << "' to non-integer data type");
}
+
// We use the 64-bit value here because it has wider range than
// any other type we use here and it allows to detect out of
// bounds conditions e.g. negative value specified for uintX_t
@@ -419,23 +450,19 @@ OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
int64_t result = 0;
try {
result = boost::lexical_cast<int64_t>(value_str);
- } catch (const boost::bad_lexical_cast& ex) {
- // Prepare error message here.
- std::string data_type_str = "boolean";
- if (OptionDataTypeTraits<T>::integer_type) {
- data_type_str = "integer";
- }
- isc_throw(BadDataTypeCast, "unable to do lexical cast to "
- << data_type_str << " data type for value "
- << value_str << ": " << ex.what());
+
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(BadDataTypeCast, "unable to convert the value '"
+ << value_str << "' to integer data type");
}
- // Perform range checks for integer values only (exclude bool values).
+ // Perform range checks.
if (OptionDataTypeTraits<T>::integer_type) {
if (result > numeric_limits<T>::max() ||
result < numeric_limits<T>::min()) {
- isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
- << value_str << ". This value is expected to be"
- << " in the range of " << numeric_limits<T>::min()
+ isc_throw(BadDataTypeCast, "unable to convert '"
+ << value_str << "' to numeric type. This value is "
+ " expected to be in the range of "
+ << numeric_limits<T>::min()
<< ".." << numeric_limits<T>::max());
}
}
@@ -458,8 +485,7 @@ OptionDefinition::writeToBuffer(const std::string& value,
// That way we actually waste 7 bits but it seems to be the
// simpler way to encode boolean.
// @todo Consider if any other encode methods can be used.
- OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value),
- buf);
+ OptionDataTypeUtil::writeBool(convertToBool(value), buf);
return;
case OPT_INT8_TYPE:
OptionDataTypeUtil::writeInt<uint8_t>
@@ -497,7 +523,7 @@ OptionDefinition::writeToBuffer(const std::string& value,
asiolink::IOAddress address(value);
if (!address.isV4() && !address.isV6()) {
isc_throw(BadDataTypeCast, "provided address "
- << address.toText()
+ << address
<< " is not a valid IPv4 or IPv6 address.");
}
OptionDataTypeUtil::writeAddress(address, buf);
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 25ce5e24e5..73c0cdfd96 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -573,19 +573,38 @@ private:
return (type == type_);
}
+ /// @brief Converts a string value to a boolean value.
+ ///
+ /// This function converts the value represented as string to a boolean
+ /// value. The following conversions are acceptable:
+ /// - "true" => true
+ /// - "false" => false
+ /// - "1" => true
+ /// - "0" => false
+ /// The first two conversions are case insensitive, so as conversions from
+ /// strings such as "TRUE", "trUE" etc. will be accepted. Note that the
+ /// only acceptable integer values, carried as strings are: "0" and "1".
+ /// For other values, e.g. "2", "3" etc. an exception will be thrown
+ /// during conversion.
+ ///
+ /// @param value_str Input value.
+ ///
+ /// @return boolean representation of the string specified as the parameter.
+ /// @throw isc::dhcp::BadDataTypeCast if failed to perform the conversion.
+ bool convertToBool(const std::string& value_str) const;
+
/// @brief Perform lexical cast of the value and validate its range.
///
/// This function performs lexical cast of a string value to integer
- /// or boolean value and checks if the resulting value is within a
- /// range of a target type. Note that range checks are not performed
- /// on boolean values. The target type should be one of the supported
- /// integer types or bool.
+ /// value and checks if the resulting value is within a range of a
+ /// target type. The target type should be one of the supported
+ /// integer types.
///
/// @param value_str input value given as string.
- /// @tparam T target type for lexical cast.
+ /// @tparam T target integer type for lexical cast.
///
- /// @return cast value.
- /// @throw BadDataTypeCast if cast was not successful.
+ /// @return Integer value after conversion from the string.
+ /// @throw isc::dhcp::BadDataTypeCast if conversion was not successful.
template<typename T>
T lexicalCastWithRangeCheck(const std::string& value_str) const;
diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h
index cbdbcb0a3a..f11b4eba1f 100644
--- a/src/lib/dhcp/option_int.h
+++ b/src/lib/dhcp/option_int.h
@@ -142,10 +142,12 @@ public:
value_ = *begin;
break;
case 2:
- value_ = isc::util::readUint16(&(*begin));
+ value_ = isc::util::readUint16(&(*begin),
+ std::distance(begin, end));
break;
case 4:
- value_ = isc::util::readUint32(&(*begin));
+ value_ = isc::util::readUint32(&(*begin),
+ std::distance(begin, end));
break;
default:
isc_throw(dhcp::InvalidDataType, "non-integer type");
diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h
index e0e93562d4..62e121b089 100644
--- a/src/lib/dhcp/option_int_array.h
+++ b/src/lib/dhcp/option_int_array.h
@@ -201,10 +201,12 @@ public:
values_.push_back(*begin);
break;
case 2:
- values_.push_back(isc::util::readUint16(&(*begin)));
+ values_.push_back(isc::util::readUint16(&(*begin),
+ std::distance(begin, end)));
break;
case 4:
- values_.push_back(isc::util::readUint32(&(*begin)));
+ values_.push_back(isc::util::readUint32(&(*begin),
+ std::distance(begin, end)));
break;
default:
isc_throw(dhcp::InvalidDataType, "non-integer type");
diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc
index 878b534a50..0d5c550ba4 100644
--- a/src/lib/dhcp/option_vendor.cc
+++ b/src/lib/dhcp/option_vendor.cc
@@ -54,7 +54,7 @@ void OptionVendor::unpack(OptionBufferConstIter begin,
<< ", length=" << distance(begin, end));
}
- vendor_id_ = isc::util::readUint32(&(*begin));
+ vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
OptionBuffer vendor_buffer(begin +4, end);
diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h
index 5b43508d05..bb8395cce5 100644
--- a/src/lib/dhcp/option_vendor.h
+++ b/src/lib/dhcp/option_vendor.h
@@ -25,6 +25,11 @@
namespace isc {
namespace dhcp {
+/// Indexes for fields in vendor-class (17) DHCPv6 option
+const int VENDOR_CLASS_ENTERPRISE_ID_INDEX = 0;
+const int VENDOR_CLASS_DATA_LEN_INDEX = 1;
+const int VENDOR_CLASS_STRING_INDEX = 2;
+
/// @brief This class represents vendor-specific information option.
///
/// As specified in RFC3925, the option formatting is slightly different
@@ -72,7 +77,7 @@ public:
/// @brief Sets enterprise identifier
///
- /// @param value vendor identifier
+ /// @param vendor_id vendor identifier
void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
/// @brief Returns enterprise identifier
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index e9c2093974..b641a03a40 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -278,8 +278,8 @@ void Pkt4::repack() {
std::string
Pkt4::toText() {
stringstream tmp;
- tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
- << " remoteAddr=" << remote_addr_.toText()
+ tmp << "localAddr=" << local_addr_ << ":" << local_port_
+ << " remoteAddr=" << remote_addr_
<< ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
<< ", transid=0x" << hex << transid_ << dec << endl;
@@ -480,7 +480,17 @@ Pkt4::isRelayed() const {
<< static_cast<int>(getHops()) << ". Valid values"
" are: (giaddr = 0 and hops = 0) or (giaddr != 0 and"
"hops != 0)");
+}
+
+bool Pkt4::inClass(const std::string& client_class) {
+ return (classes_.find(client_class) != classes_.end());
+}
+void
+Pkt4::addClass(const std::string& client_class) {
+ if (classes_.find(client_class) == classes_.end()) {
+ classes_.insert(client_class);
+ }
}
} // end of namespace isc::dhcp
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 3d9e0abf45..485d28a9dc 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -26,6 +26,7 @@
#include <iostream>
#include <vector>
+#include <set>
#include <time.h>
@@ -52,6 +53,9 @@ public:
/// to check whether client requested broadcast response.
const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
+ /// Container for storing client classes
+ typedef std::set<std::string> Classes;
+
/// Constructor, used in replying to a message.
///
/// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -550,6 +554,37 @@ public:
/// performance).
std::vector<uint8_t> data_;
+ /// @brief Checks whether a client belongs to a given class
+ ///
+ /// @param client_class name of the class
+ /// @return true if belongs
+ bool inClass(const std::string& client_class);
+
+ /// @brief Adds packet to a specified class
+ ///
+ /// A packet can be added to the same class repeatedly. Any additional
+ /// attempts to add to a class the packet already belongs to, will be
+ /// ignored silently.
+ ///
+ /// @note It is a matter of naming convention. Conceptually, the server
+ /// processes a stream of packets, with some packets belonging to given
+ /// classes. From that perspective, this method adds a packet to specifed
+ /// class. Implementation wise, it looks the opposite - the class name
+ /// is added to the packet. Perhaps the most appropriate name for this
+ /// method would be associateWithClass()? But that seems overly long,
+ /// so I decided to stick with addClass().
+ ///
+ /// @param client_class name of the class to be added
+ void addClass(const std::string& client_class);
+
+ /// @brief Classes this packet belongs to.
+ ///
+ /// This field is public, so the code outside of Pkt4 class can iterate over
+ /// existing classes. Having it public also solves the problem of returned
+ /// reference lifetime. It is preferred to use @ref inClass and @ref addClass
+ /// should be used to operate on this field.
+ Classes classes_;
+
private:
/// @brief Generic method that validates and sets HW address.
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 308880edd8..fdbd18cd0e 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -453,8 +453,8 @@ Pkt6::unpackTCP() {
std::string
Pkt6::toText() {
stringstream tmp;
- tmp << "localAddr=[" << local_addr_.toText() << "]:" << local_port_
- << " remoteAddr=[" << remote_addr_.toText()
+ tmp << "localAddr=[" << local_addr_ << "]:" << local_port_
+ << " remoteAddr=[" << remote_addr_
<< "]:" << remote_port_ << endl;
tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
hex << transid_ << dec << endl;
@@ -586,6 +586,17 @@ void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
}
}
+bool
+Pkt6::inClass(const std::string& client_class) {
+ return (classes_.find(client_class) != classes_.end());
+}
+
+void
+Pkt6::addClass(const std::string& client_class) {
+ if (classes_.find(client_class) == classes_.end()) {
+ classes_.insert(client_class);
+ }
+}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 702c424864..db80fb9996 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -23,6 +23,7 @@
#include <boost/shared_ptr.hpp>
#include <iostream>
+#include <set>
#include <time.h>
@@ -47,6 +48,9 @@ public:
TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
};
+ /// Container for storing client classes
+ typedef std::set<std::string> Classes;
+
/// @brief defines relay search pattern
///
/// Defines order in which options are searched in a message that
@@ -426,6 +430,35 @@ public:
/// data format change etc.
OptionBuffer data_;
+ /// @brief Checks whether a client belongs to a given class
+ ///
+ /// @param client_class name of the class
+ /// @return true if belongs
+ bool inClass(const std::string& client_class);
+
+ /// @brief Adds packet to a specified class
+ ///
+ /// A packet can be added to the same class repeatedly. Any additional
+ /// attempts to add to a class the packet already belongs to, will be
+ /// ignored silently.
+ ///
+ /// @note It is a matter of naming convention. Conceptually, the server
+ /// processes a stream of packets, with some packets belonging to given
+ /// classes. From that perspective, this method adds a packet to specifed
+ /// class. Implementation wise, it looks the opposite - the class name
+ /// is added to the packet. Perhaps the most appropriate name for this
+ /// method would be associateWithClass()? But that seems overly long,
+ /// so I decided to stick with addClass().
+ ///
+ /// @param client_class name of the class to be added
+ void addClass(const std::string& client_class);
+
+ /// @brief Classes this packet belongs to.
+ ///
+ /// This field is public, so code can iterate over existing classes.
+ /// Having it public also solves the problem of returned reference lifetime.
+ Classes classes_;
+
protected:
/// Builds on wire packet for TCP transmission.
///
diff --git a/src/lib/dhcp/pkt_filter.cc b/src/lib/dhcp/pkt_filter.cc
index 9c1995df1b..1eef9c7ca7 100644
--- a/src/lib/dhcp/pkt_filter.cc
+++ b/src/lib/dhcp/pkt_filter.cc
@@ -29,7 +29,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
isc_throw(SocketConfigError, "failed to create fallback socket for"
- " address " << addr.toText() << ", port " << port
+ " address " << addr << ", port " << port
<< ", reason: " << strerror(errno));
}
// Bind the socket to a specified address and port.
@@ -44,7 +44,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
// Remember to close the socket if we failed to bind it.
close(sock);
isc_throw(SocketConfigError, "failed to bind fallback socket to"
- " address " << addr.toText() << ", port " << port
+ " address " << addr << ", port " << port
<< ", reason: " << strerror(errno)
<< " - is another DHCP server running?");
}
@@ -54,7 +54,7 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
close(sock);
isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
- " fallback socket, bound to " << addr.toText() << ", port "
+ " fallback socket, bound to " << addr << ", port "
<< port << ", reason: " << strerror(errno));
}
// Successfully created and bound a fallback socket. Return a descriptor.
diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc
index 1694798538..1af39705f6 100644
--- a/src/lib/dhcp/pkt_filter_inet.cc
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -79,7 +79,8 @@ PktFilterInet::openSocket(const Iface& iface,
if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
close(sock);
- isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
+ isc_throw(SocketConfigError, "Failed to bind socket " << sock
+ << " to " << addr
<< "/port=" << port);
}
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 9f6455338f..6611f199e6 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -160,7 +160,7 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
{ "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
{ "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
- OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
{ "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
@@ -230,7 +230,8 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
// status-code
RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
// vendor-class
-RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_UINT16_TYPE,
+ OPT_STRING_TYPE);
/// Standard DHCPv6 option definitions.
///
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 5d055424c4..b95b4de1da 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -269,6 +269,7 @@ public:
}
}
}
+
};
/// @brief A test fixture class for IfaceMgr.
@@ -427,10 +428,10 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
cout << " pkt->remote_addr_ = IOAddress(\""
- << pkt->remote_addr_.toText() << "\");" << endl;
+ << pkt->remote_addr_ << "\");" << endl;
cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
cout << " pkt->local_addr_ = IOAddress(\""
- << pkt->local_addr_.toText() << "\");" << endl;
+ << pkt->local_addr_ << "\");" << endl;
cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
@@ -1014,7 +1015,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
rcvPkt->data_.size()));
- EXPECT_EQ(sendPkt->getRemoteAddr().toText(), rcvPkt->getRemoteAddr().toText());
+ EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr());
// since we opened 2 sockets on the same interface and none of them is multicast,
// none is preferred over the other for sending data, so we really should not
@@ -1503,6 +1504,43 @@ TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
}
+// This test verifies that the function correctly checks that the v4 socket is
+// open and bound to a specific address.
+TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ // Use the custom packet filter object. This object mimics the socket
+ // opening operation - the real socket is not open.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+
+ // Expect that the sockets are open on both eth0 and eth1.
+ ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+ ASSERT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+ // Socket shouldn't have been opened on loopback.
+ ASSERT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+
+ // Check that there are sockets bound to addresses that we have
+ // set for interfaces.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("192.0.2.3")));
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1")));
+ // Check that there is no socket for the address which is not
+ // configured on any interface.
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("10.1.1.1")));
+
+ // Check that v4 sockets are open, but no v6 socket is open.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET));
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET6));
+
+}
+
// This test checks that the sockets are open and bound to link local addresses
// only, if unicast addresses are not specified.
TEST_F(IfaceMgrTest, openSockets6LinkLocal) {
@@ -1830,32 +1868,6 @@ TEST_F(IfaceMgrTest, openSockets6NoIfaces) {
EXPECT_FALSE(socket_open);
}
-// Test that exception is thrown when trying to bind a new socket to the port
-// and address which is already in use by another socket.
-TEST_F(IfaceMgrTest, openSockets6NoErrorHandler) {
- NakedIfaceMgr ifacemgr;
-
- // Remove all real interfaces and create a set of dummy interfaces.
- ifacemgr.createIfaces();
-
- boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
- ASSERT_TRUE(filter);
- ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
-
- // Open socket on eth0. The openSockets6 should detect that this
- // socket has been already open and an attempt to open another socket
- // and bind to this address and port should fail.
- ASSERT_NO_THROW(ifacemgr.openSocket("eth0",
- IOAddress("fe80::3a60:77ff:fed5:cdef"),
- DHCP6_SERVER_PORT));
-
- // The function throws an exception when it tries to open a socket
- // and bind it to the address in use.
- EXPECT_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT),
- isc::dhcp::SocketConfigError);
-
-}
-
// Test that the external error handler is called when trying to bind a new
// socket to the address and port being in use. The sockets on the other
// interfaces should open just fine.
@@ -1897,6 +1909,41 @@ TEST_F(IfaceMgrTest, openSocket6ErrorHandler) {
}
+// This test verifies that the function correctly checks that the v6 socket is
+// open and bound to a specific address.
+TEST_F(IfaceMgrTest, hasOpenSocketForAddress6) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Make sure that the sockets are bound as expected.
+ ASSERT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // There should be v6 sockets only, no v4 sockets.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET6));
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET));
+
+ // Check that there are sockets bound to the addresses we have configured
+ // for interfaces.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:abcd")));
+ // Check that there is no socket bound to the address which hasn't been
+ // configured on any interface.
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:feed:1")));
+
+}
+
// Test the Iface structure itself
TEST_F(IfaceMgrTest, iface) {
boost::scoped_ptr<Iface> iface;
@@ -2289,7 +2336,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
const Iface::AddressCollection& addrs = i->getAddresses();
for (Iface::AddressCollection::const_iterator a= addrs.begin();
a != addrs.end(); ++a) {
- cout << a->toText() << " ";
+ cout << *a << " ";
}
cout << endl;
}
@@ -2347,7 +2394,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
<< " address on " << detected->getFullName() << " interface." << endl;
FAIL();
}
- cout << "Address " << addr->toText() << " on interface " << detected->getFullName()
+ cout << "Address " << *addr << " on interface " << detected->getFullName()
<< " matched with 'ifconfig -a' output." << endl;
}
}
@@ -2543,15 +2590,19 @@ TEST_F(IfaceMgrTest, detectIfaces) {
}
volatile bool callback_ok;
+volatile bool callback2_ok;
void my_callback(void) {
- cout << "Callback triggered." << endl;
callback_ok = true;
}
-TEST_F(IfaceMgrTest, controlSession) {
- // Tests if extra control socket and its callback can be passed and
- // it is supported properly by receive4() method.
+void my_callback2(void) {
+ callback2_ok = true;
+}
+
+// Tests if a single external socket and its callback can be passed and
+// it is supported properly by receive4() method.
+TEST_F(IfaceMgrTest, SingleExternalSocket4) {
callback_ok = false;
@@ -2560,7 +2611,7 @@ TEST_F(IfaceMgrTest, controlSession) {
// Create pipe and register it as extra socket
int pipefd[2];
EXPECT_TRUE(pipe(pipefd) == 0);
- EXPECT_NO_THROW(ifacemgr->set_session_socket(pipefd[0], my_callback));
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
Pkt4Ptr pkt4;
ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
@@ -2588,6 +2639,305 @@ TEST_F(IfaceMgrTest, controlSession) {
close(pipefd[0]);
}
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive4() method.
+TEST_F(IfaceMgrTest, MiltipleExternalSockets4) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // Our callbacks should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // Now, send some data over the first pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // Read the data sent, because our test callbacks are too dumb to actually
+ // do it. We don't care about the content read, because we're testing
+ // the callbacks, not pipes.
+ char buf[80];
+ EXPECT_EQ(38, read(pipefd[0], buf, 80));
+
+ // Clear the status...
+ callback_ok = false;
+ callback2_ok = false;
+
+ // And try again, using the second pipe
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests if existing external socket can be deleted and that such deletion does
+// not affect any other existing sockets. Tests uses receive4()
+TEST_F(IfaceMgrTest, DeleteExternalSockets4) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ // Now delete the first session socket
+ EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0]));
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // Let's reset the status
+ callback_ok = false;
+ callback2_ok = false;
+
+ // Now let's send something over the first callback that was unregistered.
+ // We should NOT receive any callback.
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Now check that the first callback is NOT called.
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+ EXPECT_FALSE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+
+// Tests if a single external socket and its callback can be passed and
+// it is supported properly by receive6() method.
+TEST_F(IfaceMgrTest, SingleExternalSocket6) {
+
+ callback_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // Our callback should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // Now, send some data over pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+}
+
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive6() method.
+TEST_F(IfaceMgrTest, MiltipleExternalSockets6) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // Our callbacks should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // Now, send some data over the first pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // Read the data sent, because our test callbacks are too dumb to actually
+ // do it. We don't care about the content read, because we're testing
+ // the callbacks, not pipes.
+ char buf[80];
+ EXPECT_EQ(38, read(pipefd[0], buf, 80));
+
+ // Clear the status...
+ callback_ok = false;
+ callback2_ok = false;
+
+ // And try again, using the second pipe
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests if existing external socket can be deleted and that such deletion does
+// not affect any other existing sockets. Tests uses receive6()
+TEST_F(IfaceMgrTest, DeleteExternalSockets6) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ // Now delete the first session socket
+ EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0]));
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // Let's reset the status
+ callback_ok = false;
+ callback2_ok = false;
+
+ // Now let's send something over the first callback that was unregistered.
+ // We should NOT receive any callback.
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Now check that the first callback is NOT called.
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+ EXPECT_FALSE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+
// Test checks if the unicast sockets can be opened.
// This test is now disabled, because there is no reliable way to test it. We
// can't even use loopback, beacuse openSockets() skips loopback interface
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index ec9dd9f291..4d645a45f4 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -30,6 +30,9 @@
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
@@ -890,7 +893,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionInt<uint32_t>));
LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
- typeid(Option));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
typeid(Option));
@@ -1122,4 +1125,45 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
typeid(Option6AddrLst));
}
+// tests whether v6 vendor-class option can be parsed properly.
+TEST_F(LibDhcpTest, vendorClass6) {
+
+ isc::dhcp::OptionCollection options; // Will store parsed option here
+
+ // Exported from wireshark: vendor-class option with enterprise-id = 4491
+ // and a single data entry containing "eRouter1.0"
+ string vendor_class_hex = "001000100000118b000a65526f75746572312e30";
+ OptionBuffer bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(vendor_class_hex, bin);
+
+ ASSERT_NO_THROW ({
+ LibDHCP::unpackOptions6(bin, "dhcp6", options);
+ });
+
+ EXPECT_EQ(options.size(), 1); // There should be 1 option.
+
+ // Option vendor-class should be there
+ ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end());
+
+ // It should be of type OptionCustom
+ boost::shared_ptr<OptionCustom> vclass =
+ boost::dynamic_pointer_cast<OptionCustom> (options.begin()->second);
+ ASSERT_TRUE(vclass);
+
+ // Let's investigate if the option content is correct
+
+ // 3 fields expected: vendor-id, data-len and data
+ ASSERT_EQ(3, vclass->getDataFieldsNum());
+
+ EXPECT_EQ(4491, vclass->readInteger<uint32_t>
+ (VENDOR_CLASS_ENTERPRISE_ID_INDEX)); // vendor-id=4491
+ EXPECT_EQ(10, vclass->readInteger<uint16_t>
+ (VENDOR_CLASS_DATA_LEN_INDEX)); // data len = 10
+ EXPECT_EQ("eRouter1.0", vclass->readString
+ (VENDOR_CLASS_STRING_INDEX)); // data="eRouter1.0"
}
+
+} // end of anonymous space
diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
index cdd7c6476d..ae658d2ccb 100644
--- a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
+++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -615,6 +615,7 @@ TEST(Option4ClientFqdnTest, setDomainName) {
// Empty domain name (partial). This should be successful.
ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
// Fully qualified domain-names must not be empty.
EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
index d644a2e662..dac38e1c99 100644
--- a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
+++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -537,6 +537,7 @@ TEST(Option6ClientFqdnTest, setDomainName) {
// Empty domain name (partial). This should be successful.
ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
// Fully qualified domain-names must not be empty.
EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 4add2d8673..6b181e661a 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -667,7 +667,7 @@ TEST_F(OptionCustomTest, ipv4AddressDataArray) {
for (int i = 0; i < 3; ++i) {
IOAddress address("10.10.10.10");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
// Check that it is ok if buffer length is not a multiple of IPv4
@@ -717,7 +717,7 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
for (int i = 0; i < 3; ++i) {
IOAddress address("fe80::4");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
// Check that it is ok if buffer length is not a multiple of IPv6
@@ -1451,7 +1451,7 @@ TEST_F(OptionCustomTest, unpack) {
for (int i = 0; i < 3; ++i) {
IOAddress address("10.10.10.10");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
// Remove all addresses we had added. We are going to replace
@@ -1478,7 +1478,7 @@ TEST_F(OptionCustomTest, unpack) {
for (int i = 0; i < 2; ++i) {
IOAddress address("10.10.10.10");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
}
@@ -1513,7 +1513,7 @@ TEST_F(OptionCustomTest, initialize) {
for (int i = 0; i < 3; ++i) {
IOAddress address("fe80::4");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
// Clear addresses we had previously added.
@@ -1539,7 +1539,7 @@ TEST_F(OptionCustomTest, initialize) {
for (int i = 0; i < 2; ++i) {
IOAddress address("10.10.10.10");
ASSERT_NO_THROW(address = option->readAddress(i));
- EXPECT_EQ(addresses[i].toText(), address.toText());
+ EXPECT_EQ(addresses[i], address);
}
}
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
index 717a330410..a6a33b268e 100644
--- a/src/lib/dhcp/tests/option_data_types_unittest.cc
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -93,7 +93,7 @@ TEST_F(OptionDataTypesTest, readAddress) {
// Check that the read address matches address that
// we used as input.
- EXPECT_EQ(address.toText(), address_out.toText());
+ EXPECT_EQ(address, address_out);
// Check that an attempt to read the buffer as IPv6 address
// causes an error as the IPv6 address needs at least 16 bytes
@@ -109,7 +109,7 @@ TEST_F(OptionDataTypesTest, readAddress) {
address = asiolink::IOAddress("2001:db8:1:0::1");
writeAddress(address, buf);
EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
- EXPECT_EQ(address.toText(), address_out.toText());
+ EXPECT_EQ(address, address_out);
// Truncate the buffer and expect an error to be reported when
// trying to read it.
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 357ed562c8..1ba68a8a7b 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -663,6 +663,112 @@ TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
EXPECT_EQ(5678, option_cast_v6->getValid());
}
+// The purpose of this test is to verify that the definition for option
+// that comprises a boolean value can be created and that this definition
+// can be used to create and option with a single boolean value.
+TEST_F(OptionDefinitionTest, boolValue) {
+ // The IP Forwarding option comprises one boolean value.
+ OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING,
+ "boolean");
+
+ OptionPtr option_v4;
+ // Use an option buffer which holds one value of 1 (true).
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ OptionBuffer(1, 1));
+ );
+ ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom));
+ // Validate parsed value in the received option.
+ boost::shared_ptr<OptionCustom> option_cast_v4 =
+ boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // Repeat the test above, but set the value to 0 (false).
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ OptionBuffer(1, 0));
+ );
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Try to provide zero-length buffer. Expect exception.
+ EXPECT_THROW(
+ opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, OptionBuffer()),
+ InvalidOptionValue
+ );
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single boolean value can be created and that this definition
+// can be used to create an option holding a single boolean value. The
+// boolean value is converted from a string which is expected to hold
+// the following values: "true", "false", "1" or "0". For all other
+// values exception should be thrown.
+TEST_F(OptionDefinitionTest, boolTokenized) {
+ OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING, "boolean");
+
+ OptionPtr option_v4;
+ std::vector<std::string> values;
+ // Specify a value for the option instance being created.
+ values.push_back("true");
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom));
+ // Validate the value.
+ OptionCustomPtr option_cast_v4 =
+ boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // Repeat the test but for "false" value this time.
+ values[0] = "false";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Check if that will work for numeric values.
+ values[0] = "0";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Swap numeric values and test if it works for "true" case.
+ values[0] = "1";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ ASSERT_TRUE(typeid(*option_v4) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // A conversion of non-numeric value to boolean should fail if
+ // this value is neither "true" nor "false".
+ values[0] = "garbage";
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values),
+ isc::dhcp::BadDataTypeCast);
+
+ // A conversion of numeric value to boolean should fail if this value
+ // is neither "0" nor "1".
+ values[0] = "2";
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values),
+ isc::dhcp::BadDataTypeCast);
+
+}
+
// The purpose of this test is to verify that definition for option that
// comprises single uint8 value can be created and that this definition
// can be used to create an option with single uint8 value.
@@ -672,7 +778,8 @@ TEST_F(OptionDefinitionTest, uint8) {
OptionPtr option_v6;
// Try to use correct buffer length = 1 byte.
ASSERT_NO_THROW(
- option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE,
+ OptionBuffer(1, 1));
);
ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint8_t>));
// Validate the value.
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index b1bb85275c..bfebe77dae 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/docsis3_option_defs.h>
#include <dhcp/option_string.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
@@ -316,10 +317,10 @@ TEST_F(Pkt4Test, fixedFields) {
EXPECT_EQ(dummySecs, pkt->getSecs());
EXPECT_EQ(dummyFlags, pkt->getFlags());
- EXPECT_EQ(dummyCiaddr.toText(), pkt->getCiaddr().toText());
- EXPECT_EQ(dummyYiaddr.toText(), pkt->getYiaddr().toText());
- EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText());
- EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText());
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ(dummyYiaddr, pkt->getYiaddr());
+ EXPECT_EQ(dummySiaddr, pkt->getSiaddr());
+ EXPECT_EQ(dummyGiaddr, pkt->getGiaddr());
// Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
// long and its length depends on hlen value (it is up to 16 bytes now).
@@ -382,10 +383,10 @@ TEST_F(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(dummySecs, pkt->getSecs());
EXPECT_EQ(dummyFlags, pkt->getFlags());
- EXPECT_EQ(dummyCiaddr.toText(), pkt->getCiaddr().toText());
- EXPECT_EQ(string("1.2.3.4"), pkt->getYiaddr().toText());
- EXPECT_EQ(string("192.0.2.255"), pkt->getSiaddr().toText());
- EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText());
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText());
+ EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText());
+ EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText());
// chaddr is always 16 bytes long and contains link-layer addr (MAC)
EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
@@ -818,7 +819,36 @@ TEST_F(Pkt4Test, isRelayed) {
// should throw an exception.
pkt.setGiaddr(IOAddress("0.0.0.0"));
EXPECT_THROW(pkt.isRelayed(), isc::BadValue);
+}
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt4Test, clientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ EXPECT_TRUE(pkt.classes_.empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ ASSERT_FALSE(pkt.classes_.empty());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.inClass("foo"));
}
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index e18b545cbd..17cb629b2b 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -22,6 +22,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
#include <util/range_utilities.h>
#include <boost/bind.hpp>
@@ -779,4 +780,34 @@ TEST_F(Pkt6Test, getAnyRelayOption) {
EXPECT_FALSE(opt);
}
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt6Test, clientClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ EXPECT_TRUE(pkt.classes_.empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ ASSERT_FALSE(pkt.classes_.empty());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.inClass("foo"));
+}
+
}
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
index 199ca27d58..971eb7f820 100644
--- a/src/lib/dhcp/tests/protocol_util_unittest.cc
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -359,7 +359,7 @@ TEST(ProtocolUtilTest, writeIpUdpHeader) {
in_buf.readData(src_addr_data, 4);
src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
);
- EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText());
+ EXPECT_EQ(IOAddress("192.0.2.1"), src_addr);
// Validate destination address.
IOAddress dest_addr("::1");
@@ -368,7 +368,7 @@ TEST(ProtocolUtilTest, writeIpUdpHeader) {
in_buf.readData(dest_addr_data, 4);
dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
);
- EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText());
+ EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr);
// UDP header starts here.
diff --git a/src/lib/dhcp_ddns/.gitignore b/src/lib/dhcp_ddns/.gitignore
index 6388b8cdd6..c632c2e100 100644
--- a/src/lib/dhcp_ddns/.gitignore
+++ b/src/lib/dhcp_ddns/.gitignore
@@ -1,2 +1,3 @@
/dhcp_ddns_messages.cc
/dhcp_ddns_messages.h
+/s-messages
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
index 4f3f1d7289..7e7174ac00 100644
--- a/src/lib/dhcp_ddns/ncr_io.cc
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -15,9 +15,39 @@
#include <dhcp_ddns/dhcp_ddns_log.h>
#include <dhcp_ddns/ncr_io.h>
+#include <boost/algorithm/string/predicate.hpp>
+
namespace isc {
namespace dhcp_ddns {
+NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
+ if (boost::iequals(protocol_str, "UDP")) {
+ return (NCR_UDP);
+ }
+
+ if (boost::iequals(protocol_str, "TCP")) {
+ return (NCR_TCP);
+ }
+
+ isc_throw(BadValue, "Invalid NameChangeRequest protocol:" << protocol_str);
+}
+
+std::string ncrProtocolToString(NameChangeProtocol protocol) {
+ switch (protocol) {
+ case NCR_UDP:
+ return ("UDP");
+ case NCR_TCP:
+ return ("TCP");
+ default:
+ break;
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << protocol << ")";
+ return (stream.str());
+}
+
+
//************************** NameChangeListener ***************************
NameChangeListener::NameChangeListener(RequestReceiveHandler&
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
index 94d08f7e80..a5e513a31e 100644
--- a/src/lib/dhcp_ddns/ncr_io.h
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -66,6 +66,35 @@
namespace isc {
namespace dhcp_ddns {
+/// @brief Defines the list of socket protocols supported.
+/// Currently only UDP is implemented.
+/// @todo TCP is intended to be implemented prior 1.0 release.
+/// @todo Give some thought to an ANY protocol which might try
+/// first as UDP then as TCP, etc.
+enum NameChangeProtocol {
+ NCR_UDP,
+ NCR_TCP
+};
+
+/// @brief Function which converts labels to NameChangeProtocol enum values.
+///
+/// @param protocol_str text to convert to an enum.
+/// Valid string values: "UDP", "TCP"
+///
+/// @return NameChangeProtocol value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
+
+/// @brief Function which converts NameChangeProtocol enums to text labels.
+///
+/// @param protocol enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrProtocolToString(NameChangeProtocol protocol);
+
/// @brief Exception thrown if an NcrListenerError encounters a general error.
class NcrListenerError : public isc::Exception {
public:
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
index 418eaa0a1f..ae1816ff53 100644
--- a/src/lib/dhcp_ddns/ncr_msg.cc
+++ b/src/lib/dhcp_ddns/ncr_msg.cc
@@ -18,15 +18,36 @@
#include <asiolink/io_error.h>
#include <cryptolink/cryptolink.h>
+#include <boost/algorithm/string/predicate.hpp>
#include <botan/sha2_32.h>
#include <sstream>
#include <limits>
+
namespace isc {
namespace dhcp_ddns {
+NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
+ if (boost::iequals(fmt_str, "JSON")) {
+ return FMT_JSON;
+ }
+
+ isc_throw(BadValue, "Invalid NameChangeRequest format:" << fmt_str);
+}
+
+
+std::string ncrFormatToString(NameChangeFormat format) {
+ if (format == FMT_JSON) {
+ return ("JSON");
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << format << ")";
+ return (stream.str());
+}
+
/********************************* D2Dhcid ************************************/
namespace {
@@ -590,7 +611,7 @@ NameChangeRequest::toText() const {
<< "Reverse Change: " << (reverse_change_ ? "yes" : "no")
<< std::endl
<< "FQDN: [" << fqdn_ << "]" << std::endl
- << "IP Address: [" << ip_io_address_.toText() << "]" << std::endl
+ << "IP Address: [" << ip_io_address_ << "]" << std::endl
<< "DHCID: [" << dhcid_.toStr() << "]" << std::endl
<< "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
<< "Lease Length: " << lease_length_ << std::endl;
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
index 47a7be642e..5dab9e8dd8 100644
--- a/src/lib/dhcp_ddns/ncr_msg.h
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -70,6 +70,25 @@ enum NameChangeFormat {
FMT_JSON
};
+/// @brief Function which converts labels to NameChangeFormat enum values.
+///
+/// @param format_str text to convert to an enum.
+/// Valid string values: "JSON"
+///
+/// @return NameChangeFormat value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str);
+
+/// @brief Function which converts NameChangeFormat enums to text labels.
+///
+/// @param format enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrFormatToString(NameChangeFormat format);
+
/// @brief Container class for handling the DHCID value within a
/// NameChangeRequest. It provides conversion to and from string for JSON
/// formatting, but stores the data internally as unsigned bytes.
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
index 9e83f7ccb2..c69ad93374 100644
--- a/src/lib/dhcp_ddns/ncr_udp.cc
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -94,7 +94,7 @@ NameChangeUDPListener::~NameChangeUDPListener() {
void
NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
// create our endpoint and bind the the low level socket to it.
- isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
// Create the low level socket.
try {
@@ -227,7 +227,7 @@ NameChangeUDPSender::~NameChangeUDPSender() {
void
NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
// create our endpoint and bind the the low level socket to it.
- isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
// Create the low level socket.
try {
@@ -252,8 +252,7 @@ NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
// Create the server endpoint
server_endpoint_.reset(new isc::asiolink::
- UDPEndpoint(server_address_.getAddress(),
- server_port_));
+ UDPEndpoint(server_address_, server_port_));
send_callback_->setDataSource(server_endpoint_);
}
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
index 9386a5fcbf..c66b8919cc 100644
--- a/src/lib/dhcp_ddns/tests/ncr_unittests.cc
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -12,7 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <util/time_utilities.h>
@@ -608,5 +608,26 @@ TEST(NameChangeRequestTest, ipAddresses) {
ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
}
+/// @brief Tests conversion of NameChangeFormat between enum and strings.
+TEST(NameChangeFormatTest, formatEnumConversion){
+ ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON);
+ ASSERT_EQ(stringToNcrFormat("jSoN"), dhcp_ddns::FMT_JSON);
+ ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON");
+}
+
+/// @brief Tests conversion of NameChangeProtocol between enum and strings.
+TEST(NameChangeProtocolTest, protocolEnumConversion){
+ ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("udP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
+ ASSERT_EQ(stringToNcrProtocol("Tcp"), dhcp_ddns::NCR_TCP);
+ ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP");
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP");
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/.gitignore b/src/lib/dhcpsrv/.gitignore
index 0b02c01a50..1f085382ce 100644
--- a/src/lib/dhcpsrv/.gitignore
+++ b/src/lib/dhcpsrv/.gitignore
@@ -1,2 +1,3 @@
/dhcpsrv_messages.cc
/dhcpsrv_messages.h
+/s-messages
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b3c420680f..69b14b2ee0 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
+libb10_dhcpsrv_la_SOURCES += d2_client.cc d2_client.h
libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
@@ -64,6 +65,7 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index dd1481e9e1..11b0700e3a 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -90,7 +90,7 @@ AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress&
const uint8_t prefix_len) {
if (!prefix.isV6()) {
isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
- "increase prefix " << prefix.toText() << ")");
+ "increase prefix " << prefix << ")");
}
// Get a buffer holding an address.
@@ -294,11 +294,12 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts,
Lease6Collection
AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
- uint32_t iaid, const IOAddress& hint,
+ const uint32_t iaid, const IOAddress& hint,
Lease::Type type, const bool fwd_dns_update,
const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
- const isc::hooks::CalloutHandlePtr& callout_handle) {
+ const isc::hooks::CalloutHandlePtr& callout_handle,
+ Lease6Collection& old_leases) {
try {
AllocatorPtr allocator = getAllocator(type);
@@ -316,37 +317,49 @@ AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
isc_throw(InvalidOperation, "DUID is mandatory for allocation");
}
- // check if there's existing lease for that subnet/duid/iaid combination.
+ // Check if there's existing lease for that subnet/duid/iaid
+ // combination.
/// @todo: Make this generic (cover temp. addrs and prefixes)
Lease6Collection existing = LeaseMgrFactory::instance().getLeases6(type,
*duid, iaid, subnet->getID());
+ // There is at least one lease for this client. We will return these
+ // leases for the client, but we may need to update FQDN information.
if (!existing.empty()) {
- // we have at least one lease already. This is a returning client,
- // probably after his reboot.
- return (existing);
+ // Return old leases so the server can see what has changed.
+ old_leases = existing;
+ return (updateFqdnData(existing, fwd_dns_update, rev_dns_update,
+ hostname, fake_allocation));
}
// check if the hint is in pool and is available
// This is equivalent of subnet->inPool(hint), but returns the pool
- Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(subnet->getPool(type, hint, false));
+ Pool6Ptr pool = boost::dynamic_pointer_cast<
+ Pool6>(subnet->getPool(type, hint, false));
if (pool) {
/// @todo: We support only one hint for now
Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(type, hint);
if (!lease) {
- /// @todo: check if the hint is reserved once we have host support
- /// implemented
-
- // the hint is valid and not currently used, let's create a lease for it
- lease = createLease6(subnet, duid, iaid, hint, pool->getLength(),
- type, fwd_dns_update, rev_dns_update,
+ /// @todo: check if the hint is reserved once we have host
+ /// support implemented
+
+ // The hint is valid and not currently used, let's create a
+ // lease for it
+ lease = createLease6(subnet, duid, iaid, hint,
+ pool->getLength(), type,
+ fwd_dns_update, rev_dns_update,
hostname, callout_handle, fake_allocation);
- // It can happen that the lease allocation failed (we could have lost
- // the race condition. That means that the hint is lo longer usable and
- // we need to continue the regular allocation path.
+ // It can happen that the lease allocation failed (we could
+ // have lost the race condition. That means that the hint is
+ // lo longer usable and we need to continue the regular
+ // allocation path.
if (lease) {
+ // We are allocating a new lease (not renewing). So, the
+ // old lease should be NULL.
+ old_leases.push_back(Lease6Ptr());
+
/// @todo: We support only one lease per ia for now
Lease6Collection collection;
collection.push_back(lease);
@@ -354,6 +367,11 @@ AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
}
} else {
if (lease->expired()) {
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*lease));
+ old_leases.push_back(old_lease);
+
/// We found a lease and it is expired, so we can reuse it
lease = reuseExpiredLease(lease, subnet, duid, iaid,
pool->getLength(),
@@ -414,6 +432,10 @@ AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
rev_dns_update, hostname,
callout_handle, fake_allocation);
if (lease) {
+ // We are allocating a new lease (not renewing). So, the
+ // old lease should be NULL.
+ old_leases.push_back(Lease6Ptr());
+
Lease6Collection collection;
collection.push_back(lease);
return (collection);
@@ -424,6 +446,11 @@ AllocEngine::allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
// allocation attempts.
} else {
if (existing->expired()) {
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*existing));
+ old_leases.push_back(old_lease);
+
existing = reuseExpiredLease(existing, subnet, duid, iaid,
prefix_len, fwd_dns_update,
rev_dns_update, hostname,
@@ -1037,6 +1064,30 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
}
}
+Lease6Collection
+AllocEngine::updateFqdnData(const Lease6Collection& leases,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const bool fake_allocation) {
+ Lease6Collection updated_leases;
+ for (Lease6Collection::const_iterator lease_it = leases.begin();
+ lease_it != leases.end(); ++lease_it) {
+ Lease6Ptr lease(new Lease6(**lease_it));
+ lease->fqdn_fwd_ = fwd_dns_update;
+ lease->fqdn_rev_ = rev_dns_update;
+ lease->hostname_ = hostname;
+ if (!fake_allocation &&
+ ((lease->fqdn_fwd_ != (*lease_it)->fqdn_fwd_) ||
+ (lease->fqdn_rev_ != (*lease_it)->fqdn_rev_) ||
+ (lease->hostname_ != (*lease_it)->hostname_))) {
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+ updated_leases.push_back(lease);
+ }
+ return (updated_leases);
+}
+
AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 8299bb8512..ed2a767266 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -338,14 +338,21 @@ protected:
/// an address for SOLICIT that is not really allocated (true)
/// @param callout_handle a callout handle (used in hooks). A lease callouts
/// will be executed if this parameter is passed.
+ /// @param [out] old_leases Collection to which this function will append
+ /// old leases. Leases are stored in the same order as in the
+ /// collection of new leases, being returned. For newly allocated
+ /// leases (not renewed) the NULL pointers are stored in this
+ /// collection as old leases.
///
/// @return Allocated IPv6 leases (may be empty if allocation failed)
Lease6Collection
- allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid, uint32_t iaid,
+ allocateLeases6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ const uint32_t iaid,
const isc::asiolink::IOAddress& hint, Lease::Type type,
const bool fwd_dns_update, const bool rev_dns_update,
const std::string& hostname, bool fake_allocation,
- const isc::hooks::CalloutHandlePtr& callout_handle);
+ const isc::hooks::CalloutHandlePtr& callout_handle,
+ Lease6Collection& old_leases);
/// @brief returns allocator for a given pool type
/// @param type type of pool (V4, IA, TA or PD)
@@ -489,6 +496,28 @@ private:
const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation = false);
+ /// @brief Updates FQDN data for a collection of leases.
+ ///
+ /// @param leases Collection of leases for which FQDN data should be
+ /// updated.
+ /// @param fwd_dns_update Boolean value which indicates whether forward FQDN
+ /// update was performed for each lease (true) or not (false).
+ /// @param rev_dns_update Boolean value which indicates whether reverse FQDN
+ /// update was performed for each lease (true) or not (false).
+ /// @param hostname Client hostname associated with a lease.
+ /// @param fake_allocation Boolean value which indicates that it is a real
+ /// lease allocation, e.g. Request message is processed (false), or address
+ /// is just being picked as a result of processing Solicit (true). In the
+ /// latter case, the FQDN data should not be updated in the lease database.
+ ///
+ /// @return Collection of leases with updated FQDN data. Note that returned
+ /// collection holds updated FQDN data even for fake allocation.
+ Lease6Collection updateFqdnData(const Lease6Collection& leases,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const bool fake_allocation);
+
/// @brief a pointer to currently used allocator
///
/// For IPv4, there will be only one allocator: TYPE_V4
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7789c74454..798d508f12 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -152,7 +152,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
- if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+ if ((subnets6_.size() == 1) && hint.isV6LinkLocal()) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_ONLY_SUBNET6)
.arg(subnets6_[0]->toText()).arg(hint.toText());
@@ -348,9 +348,30 @@ CfgMgr::getUnicast(const std::string& iface) const {
return (&(*addr).second);
}
+void
+CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+ d2_client_mgr_.setD2ClientConfig(new_config);
+}
+
+bool
+CfgMgr::ddnsEnabled() {
+ return (d2_client_mgr_.ddnsEnabled());
+}
+
+const D2ClientConfigPtr&
+CfgMgr::getD2ClientConfig() const {
+ return (d2_client_mgr_.getD2ClientConfig());
+}
+
+D2ClientMgr&
+CfgMgr::getD2ClientMgr() {
+ return (d2_client_mgr_);
+}
+
CfgMgr::CfgMgr()
: datadir_(DHCP_DATA_DIR),
- all_ifaces_active_(false), echo_v4_client_id_(true) {
+ all_ifaces_active_(false), echo_v4_client_id_(true),
+ d2_client_mgr_() {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index cda59f2c58..18455d08f2 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
+#include <dhcpsrv/d2_client.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
@@ -332,6 +333,29 @@ public:
return (echo_v4_client_id_);
}
+ /// @brief Updates the DHCP-DDNS client configuration to the given value.
+ ///
+ /// @param new_config pointer to the new client configuration.
+ ///
+ /// @throw Underlying method(s) will throw D2ClientError if given an empty
+ /// pointer.
+ void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+ /// @brief Convenience method for checking if DHCP-DDNS updates are enabled.
+ ///
+ /// @return True if the D2 configuration is enabled.
+ bool ddnsEnabled();
+
+ /// @brief Fetches the DHCP-DDNS configuration pointer.
+ ///
+ /// @return a reference to the current configuration pointer.
+ const D2ClientConfigPtr& getD2ClientConfig() const;
+
+ /// @brief Fetches the DHCP-DDNS manager.
+ ///
+ /// @return a reference to the DHCP-DDNS manager.
+ D2ClientMgr& getD2ClientMgr();
+
protected:
/// @brief Protected constructor.
@@ -411,6 +435,9 @@ private:
/// Indicates whether v4 server should send back client-id
bool echo_v4_client_id_;
+
+ /// @brief Manages the DHCP-DDNS client and its configuration.
+ D2ClientMgr d2_client_mgr_;
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/d2_client.cc b/src/lib/dhcpsrv/d2_client.cc
new file mode 100644
index 0000000000..494c858937
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client.cc
@@ -0,0 +1,263 @@
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/d2_client.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <string>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+D2ClientConfig::D2ClientConfig(const bool enable_updates,
+ const isc::asiolink::IOAddress& server_ip,
+ const size_t server_port,
+ const dhcp_ddns::
+ NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::
+ NameChangeFormat& ncr_format,
+ const bool always_include_fqdn,
+ const bool override_no_update,
+ const bool override_client_update,
+ const bool replace_client_name,
+ const std::string& generated_prefix,
+ const std::string& qualifying_suffix)
+ : enable_updates_(enable_updates),
+ server_ip_(server_ip),
+ server_port_(server_port),
+ ncr_protocol_(ncr_protocol),
+ ncr_format_(ncr_format),
+ always_include_fqdn_(always_include_fqdn),
+ override_no_update_(override_no_update),
+ override_client_update_(override_client_update),
+ replace_client_name_(replace_client_name),
+ generated_prefix_(generated_prefix),
+ qualifying_suffix_(qualifying_suffix) {
+ validateContents();
+}
+
+D2ClientConfig::D2ClientConfig()
+ : enable_updates_(false),
+ server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
+ server_port_(0),
+ ncr_protocol_(dhcp_ddns::NCR_UDP),
+ ncr_format_(dhcp_ddns::FMT_JSON),
+ always_include_fqdn_(false),
+ override_no_update_(false),
+ override_client_update_(false),
+ replace_client_name_(false),
+ generated_prefix_("myhost"),
+ qualifying_suffix_("example.com") {
+ validateContents();
+}
+
+D2ClientConfig::~D2ClientConfig(){};
+
+void
+D2ClientConfig::validateContents() {
+ if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
+ << dhcp_ddns::ncrFormatToString(ncr_format_)
+ << " is not yet supported");
+ }
+
+ if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << " is not yet supported");
+ }
+
+ /// @todo perhaps more validation we should do yet?
+ /// Are there any invalid combinations of options we need to test against?
+}
+
+bool
+D2ClientConfig::operator == (const D2ClientConfig& other) const {
+ return ((enable_updates_ == other.enable_updates_) &&
+ (server_ip_ == other.server_ip_) &&
+ (server_port_ == other.server_port_) &&
+ (ncr_protocol_ == other.ncr_protocol_) &&
+ (ncr_format_ == other.ncr_format_) &&
+ (always_include_fqdn_ == other.always_include_fqdn_) &&
+ (override_no_update_ == other.override_no_update_) &&
+ (override_client_update_ == other.override_client_update_) &&
+ (replace_client_name_ == other.replace_client_name_) &&
+ (generated_prefix_ == other.generated_prefix_) &&
+ (qualifying_suffix_ == other.qualifying_suffix_));
+}
+
+bool
+D2ClientConfig::operator != (const D2ClientConfig& other) const {
+ return (!(*this == other));
+}
+
+std::string
+D2ClientConfig::toText() const {
+ std::ostringstream stream;
+
+ stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
+ if (enable_updates_) {
+ stream << ", server_ip: " << server_ip_.toText()
+ << ", server_port: " << server_port_
+ << ", ncr_protocol: " << ncr_protocol_
+ << ", ncr_format: " << ncr_format_
+ << ", always_include_fqdn: " << (always_include_fqdn_ ?
+ "yes" : "no")
+ << ", override_no_update: " << (override_no_update_ ?
+ "yes" : "no")
+ << ", override_client_update: " << (override_client_update_ ?
+ "yes" : "no")
+ << ", replace_client_name: " << (replace_client_name_ ?
+ "yes" : "no")
+ << ", generated_prefix: [" << generated_prefix_ << "]"
+ << ", qualifying_suffix: [" << qualifying_suffix_ << "]";
+ }
+
+ return (stream.str());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config) {
+ os << config.toText();
+ return (os);
+}
+
+D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
+ // Default constructor initializes with a disabled configuration.
+}
+
+D2ClientMgr::~D2ClientMgr(){
+}
+
+void
+D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+ if (!new_config) {
+ isc_throw(D2ClientError,
+ "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
+ }
+
+ // @todo When NameChangeSender is integrated, we will need to handle these
+ // scenarios:
+ // 1. D2 was enabled but now it is disabled
+ // - destroy the sender, flush any queued
+ // 2. D2 is still enabled but server parameters have changed
+ // - preserve any queued, reconnect based on sender parameters
+ // 3. D2 was was disabled now it is enabled.
+ // - create sender
+ //
+ // For now we just update the configuration.
+ d2_client_config_ = new_config;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
+ .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
+ "DHCP_DDNS updates enabled");
+}
+
+bool
+D2ClientMgr::ddnsEnabled() {
+ return (d2_client_config_->getEnableUpdates());
+}
+
+const D2ClientConfigPtr&
+D2ClientMgr::getD2ClientConfig() const {
+ return (d2_client_config_);
+}
+
+void
+D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
+ bool& server_s, bool& server_n) const {
+ // Per RFC 4702 & 4704, the client N and S flags allow the client to
+ // request one of three options:
+ //
+ // N flag S flag Option
+ // ------------------------------------------------------------------
+ // 0 0 client wants to do forward updates (section 3.2)
+ // 0 1 client wants server to do forward updates (section 3.3)
+ // 1 0 client wants no one to do updates (section 3.4)
+ // 1 1 invalid combination
+ // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
+ //
+ // Make a bit mask from the client's flags and use it to set the response
+ // flags accordingly.
+ const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
+
+ switch (mask) {
+ case 0:
+ // If updates are enabled and we are overriding client delegation
+ // then S flag should be true.
+ server_s = (d2_client_config_->getEnableUpdates() &&
+ d2_client_config_->getOverrideClientUpdate());
+ break;
+
+ case 1:
+ server_s = d2_client_config_->getEnableUpdates();
+ break;
+
+ case 2:
+ // If updates are enabled and we are overriding "no updates" then
+ // S flag should be true.
+ server_s = (d2_client_config_->getEnableUpdates() &&
+ d2_client_config_->getOverrideNoUpdate());
+ break;
+
+ default:
+ // RFCs declare this an invalid combination.
+ isc_throw(isc::BadValue,
+ "Invalid client FQDN - N and S cannot both be 1");
+ break;
+ }
+
+ /// @todo Currently we are operating under the premise that N should be 1
+ /// if the server is not doing updates nor do we have configuration
+ /// controls to govern forward and reverse updates independently.
+ /// In addition, the client FQDN flags cannot explicitly suggest what to
+ /// do with reverse updates. They request either forward updates or no
+ /// updates. In other words, the client cannot request the server do or
+ /// not do reverse updates. For now, we are either going to do updates in
+ /// both directions or none at all. If and when additional configuration
+ /// parameters are added this logic will have to be reassessed.
+ server_n = !server_s;
+}
+
+std::string
+D2ClientMgr::generateFqdn(const asiolink::IOAddress& address) const {
+ std::string hostname = address.toText();
+ std::replace(hostname.begin(), hostname.end(),
+ (address.isV4() ? '.' : ':'), '-');
+
+ std::ostringstream gen_name;
+ gen_name << d2_client_config_->getGeneratedPrefix() << "-" << hostname;
+ return (qualifyName(gen_name.str()));
+}
+
+std::string
+D2ClientMgr::qualifyName(const std::string& partial_name) const {
+ std::ostringstream gen_name;
+ gen_name << partial_name << "." << d2_client_config_->getQualifyingSuffix();
+
+ // Tack on a trailing dot in case suffix doesn't have one.
+ std::string str = gen_name.str();
+ size_t len = str.length();
+ if ((len > 0) && (str[len - 1] != '.')) {
+ gen_name << ".";
+ }
+
+ return (gen_name.str());
+}
+
+
+
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client.h b/src/lib/dhcpsrv/d2_client.h
new file mode 100644
index 0000000000..0a6faa4713
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client.h
@@ -0,0 +1,382 @@
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CLIENT_H
+#define D2_CLIENT_H
+
+/// @file d2_client.h Defines the D2ClientConfig and D2ClientMgr classes.
+/// This file defines the classes Kea uses to act as a client of the b10-
+/// dhcp-ddns module (aka D2).
+///
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+
+/// An exception that is thrown if an error occurs while configuring
+/// the D2 DHCP DDNS client.
+class D2ClientError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ D2ClientError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Acts as a storage vault for D2 client configuration
+///
+/// A simple container class for storing and retrieving the configuration
+/// parameters associated with DHCP-DDNS and acting as a client of D2.
+/// Instances of this class may be constructed through configuration parsing.
+///
+class D2ClientConfig {
+public:
+ /// @brief Constructor
+ ///
+ /// @param enable_updates Enables DHCP-DDNS updates
+ /// @param server_ip IP address of the b10-dhcp-ddns server (IPv4 or IPv6)
+ /// @param server_port IP port of the b10-dhcp-ddns server
+ /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
+ /// Currently only UDP is supported.
+ /// @param ncr_format Format of the b10-dhcp-ddns requests.
+ /// Currently only JSON format is supported.
+ /// @param always_include_fqdn Enables always including the FQDN option in
+ /// DHCP responses.
+ /// @param override_no_update Enables updates, even if clients request no
+ /// updates.
+ /// @param override_client_update Perform updates, even if client requested
+ /// delegation.
+ /// @param replace_client_name enables replacement of the domain-name
+ /// supplied by the client with a generated name.
+ /// @param generated_prefix Prefix to use when generating domain-names.
+ /// @param qualifying_suffix Suffix to use to qualify partial domain-names.
+ ///
+ /// @throw D2ClientError if given an invalid protocol or format.
+ D2ClientConfig(const bool enable_updates,
+ const isc::asiolink::IOAddress& server_ip,
+ const size_t server_port,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format,
+ const bool always_include_fqdn,
+ const bool override_no_update,
+ const bool override_client_update,
+ const bool replace_client_name,
+ const std::string& generated_prefix,
+ const std::string& qualifying_suffix);
+
+ /// @brief Default constructor
+ /// The default constructor creates an instance that has updates disabled.
+ D2ClientConfig();
+
+ /// @brief Destructor
+ virtual ~D2ClientConfig();
+
+ /// @brief Return whether or not DHCP-DDNS updating is enabled.
+ bool getEnableUpdates() const {
+ return(enable_updates_);
+ }
+
+ /// @brief Return the IP address of b10-dhcp-ddns (IPv4 or IPv6).
+ const isc::asiolink::IOAddress& getServerIp() const {
+ return(server_ip_);
+ }
+
+ /// @brief Return the IP port of b10-dhcp-ddns.
+ size_t getServerPort() const {
+ return(server_port_);
+ }
+
+ /// @brief Return the socket protocol to use with b10-dhcp-ddns.
+ const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+ return(ncr_protocol_);
+ }
+
+ /// @brief Return the b10-dhcp-ddns request format.
+ const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+ return(ncr_format_);
+ }
+
+ /// @brief Return whether or not FQDN is always included in DHCP responses.
+ bool getAlwaysIncludeFqdn() const {
+ return(always_include_fqdn_);
+ }
+
+ /// @brief Return if updates are done even if clients request no updates.
+ bool getOverrideNoUpdate() const {
+ return(override_no_update_);
+ }
+
+ /// @brief Return if updates are done even when clients request delegation.
+ bool getOverrideClientUpdate() const {
+ return(override_client_update_);
+ }
+
+ /// @brief Return whether or not client's domain-name is always replaced.
+ bool getReplaceClientName() const {
+ return(replace_client_name_);
+ }
+
+ /// @brief Return the prefix to use when generating domain-names.
+ const std::string& getGeneratedPrefix() const {
+ return(generated_prefix_);
+ }
+
+ /// @brief Return the suffix to use to qualify partial domain-names.
+ const std::string& getQualifyingSuffix() const {
+ return(qualifying_suffix_);
+ }
+
+ /// @brief Compares two D2ClientConfigs for equality
+ bool operator == (const D2ClientConfig& other) const;
+
+ /// @brief Compares two D2ClientConfigs for inequality
+ bool operator != (const D2ClientConfig& other) const;
+
+ /// @brief Generates a string representation of the class contents.
+ std::string toText() const;
+
+protected:
+ /// @brief Validates member values.
+ ///
+ /// Method is used by the constructor to validate member contents.
+ ///
+ /// @throw D2ClientError if given an invalid protocol or format.
+ virtual void validateContents();
+
+private:
+ /// @brief Indicates whether or not DHCP DDNS updating is enabled.
+ bool enable_updates_;
+
+ /// @brief IP address of the b10-dhcp-ddns server (IPv4 or IPv6).
+ isc::asiolink::IOAddress server_ip_;
+
+ /// @brief IP port of the b10-dhcp-ddns server.
+ size_t server_port_;
+
+ /// @brief The socket protocol to use with b10-dhcp-ddns.
+ /// Currently only UDP is supported.
+ dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+ /// @brief Format of the b10-dhcp-ddns requests.
+ /// Currently only JSON format is supported.
+ dhcp_ddns::NameChangeFormat ncr_format_;
+
+ /// @brief Should Kea always include the FQDN option in its response.
+ bool always_include_fqdn_;
+
+ /// @brief Should Kea perform updates, even if client requested no updates.
+ /// Overrides the client request for no updates via the N flag.
+ bool override_no_update_;
+
+ /// @brief Should Kea perform updates, even if client requested delegation.
+ bool override_client_update_;
+
+ /// @brief Should Kea replace the domain-name supplied by the client.
+ bool replace_client_name_;
+
+ /// @brief Prefix Kea should use when generating domain-names.
+ std::string generated_prefix_;
+
+ /// @brief Suffix Kea should use when to qualify partial domain-names.
+ std::string qualifying_suffix_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config);
+
+/// @brief Defines a pointer for D2ClientConfig instances.
+typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
+
+/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
+///
+/// Provides services for managing the current D2ClientConfig and managing
+/// communications with D2. (@todo The latter will be added once communication
+/// with D2 is implemented through the integration of
+/// dhcp_ddns::NameChangeSender interface(s)).
+///
+class D2ClientMgr {
+public:
+ /// @brief Constructor
+ ///
+ /// Default constructor which constructs an instance which has DHCP-DDNS
+ /// updates disabled.
+ D2ClientMgr();
+
+ /// @brief Destructor.
+ ~D2ClientMgr();
+
+ /// @brief Updates the DHCP-DDNS client configuration to the given value.
+ ///
+ /// @param new_config pointer to the new client configuration.
+ /// @throw D2ClientError if passed an empty pointer.
+ void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+ /// @brief Convenience method for checking if DHCP-DDNS is enabled.
+ ///
+ /// @return True if the D2 configuration is enabled.
+ bool ddnsEnabled();
+
+ /// @brief Fetches the DHCP-DDNS configuration pointer.
+ ///
+ /// @return a reference to the current configuration pointer.
+ const D2ClientConfigPtr& getD2ClientConfig() const;
+
+ /// @brief Determines server flags based on configuration and client flags.
+ ///
+ /// This method uses input values for the client's FQDN S and N flags, in
+ /// conjunction with the configuration parameters updates-enabled, override-
+ /// no-updates, and override-client-updates to determine the values that
+ /// should be used for the server's FQDN S and N flags.
+ /// The logic in this method is based upon RFCs 4702 and 4704.
+ ///
+ /// @param client_s S Flag from the client's FQDN
+ /// @param client_n N Flag from the client's FQDN
+ /// @param server_s [out] S Flag for the server's FQDN
+ /// @param server_n [out] N Flag for the server's FQDN
+ ///
+ /// @throw isc::BadValue if client_s and client_n are both 1 as this is
+ /// an invalid combination per RFCs.
+ void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
+ bool& server_n) const;
+
+ /// @brief Builds a FQDN based on the configuration and given IP address.
+ ///
+ /// Using the current values for generated-prefix, qualifying-suffix and
+ /// an IP address, this method constructs a fully qualified domain name.
+ /// It supports both IPv4 and IPv6 addresses. The format of the name
+ /// is as follows:
+ ///
+ /// <generated-prefix>-<ip address>.<qualifying-suffix>.
+ ///
+ /// <ip-address> is the result of IOAddress.toText() with the delimiters
+ /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
+ ///
+ /// @param address IP address from which to derive the name (IPv4 or IPv6)
+ ///
+ /// @return std::string containing the generated name.
+ std::string generateFqdn(const asiolink::IOAddress& address) const;
+
+ /// @brief Adds a qualifying suffix to a given domain name
+ ///
+ /// Constructs a FQDN based on the configured qualifying-suffix and
+ /// a partial domain name as follows:
+ ///
+ /// <partial_name>.<qualifying-suffix>.
+ /// Note it will add a trailing '.' should qualifying-suffix not end with
+ /// one.
+ ///
+ /// @param partial_name domain name to qualify
+ ///
+ /// @return std::string containing the qualified name.
+ std::string qualifyName(const std::string& partial_name) const;
+
+ /// @brief Set server FQDN flags based on configuration and a given FQDN
+ ///
+ /// Templated wrapper around the analyzeFqdn() allowing that method to
+ /// be used for either IPv4 or IPv6 processing. This methods resets all
+ /// of the flags in the response to zero and then sets the S,N, and O
+ /// flags. Any other flags are the responsiblity of the invoking layer.
+ ///
+ /// @param fqdn FQDN option from which to read client (inbound) flags
+ /// @param fqdn_resp FQDN option to update with the server (outbound) flags
+ /// @tparam T FQDN Option class containing the FQDN data such as
+ /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+ template <class T>
+ void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
+
+ /// @brief Set server FQDN name based on configuration and a given FQDN
+ ///
+ /// Templated method which adjusts the domain name value and type in
+ /// a server FQDN from a client (inbound) FQDN and the current
+ /// configuration. The logic is as follows:
+ ///
+ /// If replace-client-name is true or the supplied name is empty, the
+ /// server FQDN is set to ""/PARTIAL.
+ ///
+ /// If replace-client-name is false and the supplied name is a partial
+ /// name the server FQDN is set to the supplied name qualified by
+ /// appending the qualifying-suffix.
+ ///
+ /// If replace-client-name is false and the supplied name is a fully
+ /// qualified name, set the server FQDN to the supplied name.
+ ///
+ /// @param fqdn FQDN option from which to get client (inbound) name
+ /// @param fqdn_resp FQDN option to update with the adjusted name
+ /// @tparam T FQDN Option class containing the FQDN data such as
+ /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+ template <class T>
+ void adjustDomainName(const T& fqdn, T& fqdn_resp);
+
+private:
+ /// @brief Container class for DHCP-DDNS configuration parameters.
+ D2ClientConfigPtr d2_client_config_;
+};
+
+template <class T>
+void
+D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
+ bool server_s = false;
+ bool server_n = false;
+ analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
+ server_s, server_n);
+
+ // Reset the flags to zero to avoid triggering N and S both 1 check.
+ fqdn_resp.resetFlags();
+
+ // Set S and N flags.
+ fqdn_resp.setFlag(T::FLAG_S, server_s);
+ fqdn_resp.setFlag(T::FLAG_N, server_n);
+
+ // Set O flag true if server S overrides client S.
+ fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
+}
+
+
+template <class T>
+void
+D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
+ // If we're configured to replace it or the supplied name is blank
+ // set the response name to blank.
+ if (d2_client_config_->getReplaceClientName() ||
+ fqdn.getDomainName().empty()) {
+ fqdn_resp.setDomainName("", T::PARTIAL);
+ } else {
+ // If the supplied name is partial, qualify it by adding the suffix.
+ if (fqdn.getDomainNameType() == T::PARTIAL) {
+ fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
+ }
+ }
+}
+
+/// @brief Defines a pointer for D2ClientMgr instances.
+typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
+
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/dbaccess_parser.h b/src/lib/dhcpsrv/dbaccess_parser.h
index 53e3f81ea3..77c984554d 100644
--- a/src/lib/dhcpsrv/dbaccess_parser.h
+++ b/src/lib/dhcpsrv/dbaccess_parser.h
@@ -94,7 +94,8 @@ public:
///
/// Creates an instance of this parser.
///
- /// @param name Name of the parameter used to access the configuration.
+ /// @param param_name Name of the parameter used to access the
+ /// configuration.
///
/// @return Pointer to a DbAccessParser. The caller is responsible for
/// destroying the parser after use.
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index ffd49d3083..7aea31032e 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -400,15 +400,27 @@ void
OptionDataParser::createOption() {
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
- // does not exceed range of uint8_t and is not zero.
+ // does not exceed range of uint8_t for DHCPv4, uint16_t for DHCPv6 and
+ // is not zero.
uint32_t option_code = uint32_values_->getParam("code");
if (option_code == 0) {
isc_throw(DhcpConfigError, "option code must not be zero."
- << " Option code '0' is reserved in DHCPv4.");
- } else if (option_code > std::numeric_limits<uint8_t>::max()) {
+ << " Option code '0' is reserved.");
+
+ } else if (global_context_->universe_ == Option::V4 &&
+ option_code > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << option_code
+ << "', it must not exceed '"
+ << static_cast<int>(std::numeric_limits<uint8_t>::max())
+ << "'");
+
+ } else if (global_context_->universe_ == Option::V6 &&
+ option_code > std::numeric_limits<uint16_t>::max()) {
isc_throw(DhcpConfigError, "invalid option code '" << option_code
<< "', it must not exceed '"
- << std::numeric_limits<uint8_t>::max() << "'");
+ << std::numeric_limits<uint16_t>::max()
+ << "'");
+
}
// Check that the option name has been specified, is non-empty and does not
@@ -464,7 +476,7 @@ OptionDataParser::createOption() {
}
// Get option data from the configuration database ('data' field).
- const std::string option_data = string_values_->getParam("data");
+ std::string option_data = string_values_->getParam("data");
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
@@ -480,6 +492,12 @@ OptionDataParser::createOption() {
// Otherwise, the option data is specified as a string of
// hexadecimal digits that we have to turn into binary format.
try {
+ // The decodeHex function expects that the string contains an
+ // even number of digits. If we don't meet this requirement,
+ // we have to insert a leading 0.
+ if (!option_data.empty() && option_data.length() % 2) {
+ option_data = option_data.insert(0, "0");
+ }
util::encode::decodeHex(option_data, binary);
} catch (...) {
isc_throw(DhcpConfigError, "option data is not a valid"
@@ -1163,5 +1181,108 @@ SubnetConfigParser::getParam(const std::string& name) {
return (Triplet<uint32_t>(value));
}
+//**************************** D2ClientConfigParser **********************
+D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name)
+ : entry_name_(entry_name), boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
+ local_client_config_() {
+}
+
+D2ClientConfigParser::~D2ClientConfigParser() {
+}
+
+void
+D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
+ BOOST_FOREACH(ConfigPair param, client_config->mapValue()) {
+ ParserPtr parser(createConfigParser(param.first));
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ bool enable_updates = boolean_values_->getParam("enable-updates");
+ if (!enable_updates && (client_config->mapValue().size() == 1)) {
+ // If enable-updates is the only parameter and it is false then
+ // we're done. This allows for an abbreviated configuration entry
+ // that only contains that flag. Use the default D2ClientConfig
+ // constructor to a create a disabled instance.
+ local_client_config_.reset(new D2ClientConfig());
+ return;
+ }
+
+ // Get all parameters that are needed to create the D2ClientConfig.
+ asiolink::IOAddress server_ip(string_values_->getParam("server-ip"));
+
+ uint32_t server_port = uint32_values_->getParam("server-port");
+
+ dhcp_ddns::NameChangeProtocol
+ ncr_protocol = dhcp_ddns:: stringToNcrProtocol(string_values_->
+ getParam("ncr-protocol"));
+
+ dhcp_ddns::NameChangeFormat
+ ncr_format = dhcp_ddns::stringToNcrFormat(string_values_->
+ getParam("ncr-format"));
+
+ std::string generated_prefix = string_values_->getParam("generated-prefix");
+ std::string qualifying_suffix = string_values_->
+ getParam("qualifying-suffix");
+
+ bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn");
+ bool override_no_update = boolean_values_->getParam("override-no-update");
+ bool override_client_update = boolean_values_->
+ getParam("override-client-update");
+ bool replace_client_name = boolean_values_->getParam("replace-client-name");
+
+ // Attempt to create the new client config.
+ local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
+ server_port, ncr_protocol,
+ ncr_format,
+ always_include_fqdn,
+ override_no_update,
+ override_client_update,
+ replace_client_name,
+ generated_prefix,
+ qualifying_suffix));
+}
+
+isc::dhcp::ParserPtr
+D2ClientConfigParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id.compare("server-port") == 0) {
+ parser = new Uint32Parser(config_id, uint32_values_);
+ } else if ((config_id.compare("server-ip") == 0) ||
+ (config_id.compare("ncr-protocol") == 0) ||
+ (config_id.compare("ncr-format") == 0) ||
+ (config_id.compare("generated-prefix") == 0) ||
+ (config_id.compare("qualifying-suffix") == 0)) {
+ parser = new StringParser(config_id, string_values_);
+ } else if ((config_id.compare("enable-updates") == 0) ||
+ (config_id.compare("always-include-fqdn") == 0) ||
+ (config_id.compare("allow-client-update") == 0) ||
+ (config_id.compare("override-no-update") == 0) ||
+ (config_id.compare("override-client-update") == 0) ||
+ (config_id.compare("replace-client-name") == 0)) {
+ parser = new BooleanParser(config_id, boolean_values_);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: D2ClientConfig parameter not supported: "
+ << config_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+D2ClientConfigParser::commit() {
+ // @todo if local_client_config_ is empty then shutdown the listener...
+ // @todo Should this also attempt to start a listener?
+ // In keeping with Interface, Subnet, and Hooks parsers, then this
+ // should initialize the listener. Failure to init it, should cause
+ // rollback. This gets sticky, because who owns the listener instance?
+ // Does CfgMgr maintain it or does the server class? If the latter
+ // how do we get that value here?
+ // I'm thinkikng D2ClientConfig could contain the listener instance
+ CfgMgr::instance().setD2ClientConfig(local_client_config_);
+}
+
}; // namespace dhcp
}; // namespace isc
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 28c57b8b93..00a07a0a3a 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <cc/data.h>
#include <dhcp/option_definition.h>
+#include <dhcpsrv/d2_client.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/subnet.h>
@@ -243,7 +244,7 @@ public:
// its value. If it doesn't we insert a new element.
storage_->setParam(param_name_, value_);
}
-
+
private:
/// Pointer to the storage where committed value is stored.
boost::shared_ptr<ValueStorage<ValueType> > storage_;
@@ -876,6 +877,77 @@ protected:
ParserContextPtr global_context_;
};
+/// @brief Parser for D2ClientConfig
+///
+/// This class parses the configuration element "dhcp-ddns" common to the
+/// spec files for both dhcp4 and dhcp6. It creates an instance of a
+/// D2ClientConfig.
+class D2ClientConfigParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition.
+ D2ClientConfigParser(const std::string& entry_name);
+
+ /// @brief Destructor
+ virtual ~D2ClientConfigParser();
+
+ /// @brief Performs the parsing of the given dhcp-ddns element.
+ ///
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param client_config is the "dhcp-ddns" configuration to parse
+ virtual void build(isc::data::ConstElementPtr client_config);
+
+ /// @brief Creates a parser for the given "dhcp-ddns" member element id.
+ ///
+ /// The elements currently supported are (see isc::dhcp::D2ClientConfig
+ /// for details on each):
+ /// -# enable-updates
+ /// -# server-ip
+ /// -# server-port
+ /// -# ncr-protocol
+ /// -# ncr-format
+ /// -# remove-on-renew
+ /// -# always-include-fqdn
+ /// -# allow-client-update
+ /// -# override-no-update
+ /// -# override-client-update
+ /// -# replace-client-name
+ /// -# generated-prefix
+ /// -# qualifying-suffix
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "dns_server" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a D2ClientConfig from internal data values
+ /// passes to CfgMgr singleton.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Primarily used for diagnostics.
+ std::string entry_name_;
+
+ /// Storage for subnet-specific boolean values.
+ BooleanStoragePtr boolean_values_;
+
+ /// Storage for subnet-specific integer values.
+ Uint32StoragePtr uint32_values_;
+
+ /// Storage for subnet-specific string values.
+ StringStoragePtr string_values_;
+
+ /// @brief Pointer to temporary local instance created during build.
+ D2ClientConfigPtr local_client_config_ ;
+};
+
// Pointers to various parser objects.
typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 6ee3e87de7..5c7854e4d1 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -70,6 +70,9 @@ specified IPv6 subnet to its database.
A debug message issued when server is being configured to listen on all
interfaces.
+% DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1
+A debug message issued when the server's DHCP-DDNS settings are changed.
+
% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
A debug message issued when configuration manager clears the internal list
of active interfaces. This doesn't prevent the server from listening to
diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc
index 4ca6a3ce0b..7bc71f94d8 100644
--- a/src/lib/dhcpsrv/lease.cc
+++ b/src/lib/dhcpsrv/lease.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -56,6 +56,13 @@ bool Lease::expired() const {
return (expire_time < time(NULL));
}
+bool
+Lease::hasIdenticalFqdn(const Lease& other) const {
+ return (hostname_ == other.hostname_ &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_);
+}
+
Lease4::Lease4(const Lease4& other)
: Lease(other.addr_, other.t1_, other.t2_, other.valid_lft_,
other.subnet_id_, other.cltt_, other.fqdn_fwd_,
@@ -175,7 +182,7 @@ Lease6::toText() const {
stream << "Type: " << typeToText(type_) << "("
<< static_cast<int>(type_) << ") ";
- stream << "Address: " << addr_.toText() << "\n"
+ stream << "Address: " << addr_ << "\n"
<< "Prefix length: " << static_cast<int>(prefixlen_) << "\n"
<< "IAID: " << iaid_ << "\n"
<< "Pref life: " << preferred_lft_ << "\n"
@@ -190,7 +197,7 @@ std::string
Lease4::toText() const {
ostringstream stream;
- stream << "Address: " << addr_.toText() << "\n"
+ stream << "Address: " << addr_ << "\n"
<< "Valid life: " << valid_lft_ << "\n"
<< "T1: " << t1_ << "\n"
<< "T2: " << t2_ << "\n"
diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h
index 86266205c9..c767142761 100644
--- a/src/lib/dhcpsrv/lease.h
+++ b/src/lib/dhcpsrv/lease.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -141,6 +141,14 @@ struct Lease {
/// @return true if the lease is expired
bool expired() const;
+ /// @brief Returns true if the other lease has equal FQDN data.
+ ///
+ /// @param other Lease which FQDN data is to be compared with our lease.
+ ///
+ /// @return Boolean value which indicates whether FQDN data of the other
+ /// lease is equal to the FQDN data of our lease (true) or not (false).
+ bool hasIdenticalFqdn(const Lease& other) const;
+
};
/// @brief Structure that holds a lease for IPv4 address
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 9355736de3..aab7b9ed05 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -214,7 +214,7 @@ public:
/// @param subnet_id A subnet identifier.
///
/// @return A pointer to the lease or NULL if the lease is not found.
- virtual Lease4Ptr getLease4(const ClientId& clientid, const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const ClientId& client_id, const HWAddr& hwaddr,
SubnetID subnet_id) const = 0;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index dbc3bdd5ce..c6c9c9feb1 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -247,7 +247,7 @@ Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
Lease4Storage::iterator lease_it = storage4_.find(lease->addr_);
if (lease_it == storage4_.end()) {
isc_throw(NoSuchLease, "failed to update the lease with address "
- << lease->addr_.toText() << " - no such lease");
+ << lease->addr_ << " - no such lease");
}
**lease_it = *lease;
}
@@ -260,7 +260,7 @@ Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
Lease6Storage::iterator lease_it = storage6_.find(lease->addr_);
if (lease_it == storage6_.end()) {
isc_throw(NoSuchLease, "failed to update the lease with address "
- << lease->addr_.toText() << " - no such lease");
+ << lease->addr_ << " - no such lease");
}
**lease_it = *lease;
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 623440bf22..c006bbf5a5 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -1809,12 +1809,12 @@ MySqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
int affected_rows = mysql_stmt_affected_rows(statements_[stindex]);
if (affected_rows == 0) {
isc_throw(NoSuchLease, "unable to update lease for address " <<
- lease->addr_.toText() << " as it does not exist");
+ lease->addr_ << " as it does not exist");
} else if (affected_rows > 1) {
// Should not happen - primary key constraint should only have selected
// one row.
isc_throw(DbOperationError, "apparently updated more than one lease "
- "that had the address " << lease->addr_.toText());
+ "that had the address " << lease->addr_);
}
}
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index f1ba871aa5..d9c3da0e4d 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -34,8 +34,8 @@ bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
std::string
Pool::toText() const {
std::stringstream tmp;
- tmp << "type=" << Lease::typeToText(type_) << ", " << first_.toText()
- << "-" << last_.toText();
+ tmp << "type=" << Lease::typeToText(type_) << ", " << first_
+ << "-" << last_;
return (tmp.str());
}
@@ -143,8 +143,8 @@ Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
std::string
Pool6::toText() const {
std::stringstream tmp;
- tmp << "type=" << Lease::typeToText(type_) << ", " << first_.toText()
- << "-" << last_.toText() << ", delegated_len="
+ tmp << "type=" << Lease::typeToText(type_) << ", " << first_
+ << "-" << last_ << ", delegated_len="
<< static_cast<int>(prefix_len_);
return (tmp.str());
}
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index d861afe25f..0134d8c9ac 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -24,11 +24,14 @@ using namespace isc::asiolink;
namespace isc {
namespace dhcp {
+// This is an initial value of subnet-id. See comments in subnet.h for details.
+SubnetID Subnet::static_id_ = 1;
+
Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime)
- :id_(getNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
+ :id_(generateNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
t2_(t2), valid_(valid_lifetime),
last_allocated_ia_(lastAddrInPrefix(prefix, len)),
last_allocated_ta_(lastAddrInPrefix(prefix, len)),
@@ -162,7 +165,7 @@ void Subnet::setLastAllocated(Lease::Type type,
std::string
Subnet::toText() const {
std::stringstream tmp;
- tmp << prefix_.toText() << "/" << static_cast<unsigned int>(prefix_len_);
+ tmp << prefix_ << "/" << static_cast<unsigned int>(prefix_len_);
return (tmp.str());
}
@@ -187,7 +190,7 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) {
if (!siaddr.isV4()) {
isc_throw(BadValue, "Can't set siaddr to non-IPv4 address "
- << siaddr.toText());
+ << siaddr);
}
siaddr_ = siaddr;
}
@@ -263,9 +266,8 @@ Subnet::addPool(const PoolPtr& pool) {
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool (" << first_addr.toText() << "-"
- << last_addr.toText()
- << " does not belong in this (" << prefix_.toText() << "/"
+ isc_throw(BadValue, "Pool (" << first_addr << "-" << last_addr
+ << " does not belong in this (" << prefix_ << "/"
<< static_cast<int>(prefix_len_) << ") subnet");
}
@@ -332,7 +334,7 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
:Subnet(prefix, length, t1, t2, valid_lifetime),
preferred_(preferred_lifetime){
if (!prefix.isV6()) {
- isc_throw(BadValue, "Non IPv6 prefix " << prefix.toText()
+ isc_throw(BadValue, "Non IPv6 prefix " << prefix
<< " specified in subnet6");
}
}
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index 31dc9477af..ecac6c3af8 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -366,6 +366,15 @@ public:
/// @return textual representation
virtual std::string toText() const;
+ /// @brief Resets subnet-id counter to its initial value (1)
+ ///
+ /// This should be called during reconfiguration, before any new
+ /// subnet objects are created. It will ensure that the subnet_id will
+ /// be consistent between reconfigures.
+ static void resetSubnetID() {
+ static_id_ = 1;
+ }
+
protected:
/// @brief Returns all pools (non-const variant)
///
@@ -378,7 +387,12 @@ protected:
/// @brief Protected constructor
//
/// By making the constructor protected, we make sure that noone will
- /// ever instantiate that class. Pool4 and Pool6 should be used instead.
+ /// ever instantiate that class. Subnet4 and Subnet6 should be used instead.
+ ///
+ /// This constructor assigns a new subnet-id (see @ref generateNextID).
+ /// This subnet-id has unique value that is strictly monotonously increasing
+ /// for each subnet, until it is explicitly reset back to 1 during
+ /// reconfiguration process.
Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
@@ -390,12 +404,24 @@ protected:
/// derive from this class.
virtual ~Subnet() { };
+ /// @brief keeps the subnet-id value
+ ///
+ /// It is inreased every time a new Subnet object is created.
+ /// It is reset (@ref resetSubnetId) every time reconfiguration occurs.
+ ///
+ /// Static value initialized in subnet.cc.
+ static SubnetID static_id_;
+
/// @brief returns the next unique Subnet-ID
///
+ /// This method generates and returns the next unique subnet-id.
+ /// It is a strictly monotonously increasing value (1,2,3,...) for
+ /// each new Subnet object created. It can be explicitly reset
+ /// back to 1 during reconfiguration (@ref resetSubnetID).
+ ///
/// @return the next unique Subnet-ID
- static SubnetID getNextID() {
- static SubnetID id = 0;
- return (id++);
+ static SubnetID generateNextID() {
+ return (static_id_++);
}
/// @brief Checks if used pool type is valid
@@ -495,6 +521,8 @@ public:
/// @brief Constructor with all parameters
///
+ /// This constructor calls Subnet::Subnet, where subnet-id is generated.
+ ///
/// @param prefix Subnet4 prefix
/// @param length prefix length
/// @param t1 renewal timer (in seconds)
@@ -559,6 +587,8 @@ public:
/// @brief Constructor with all parameters
///
+ /// This constructor calls Subnet::Subnet, where subnet-id is generated.
+ ///
/// @param prefix Subnet6 prefix
/// @param length prefix length
/// @param t1 renewal timer (in seconds)
diff --git a/src/lib/dhcpsrv/tests/.gitignore b/src/lib/dhcpsrv/tests/.gitignore
index 7add7fb019..33ac8d9e86 100644
--- a/src/lib/dhcpsrv/tests/.gitignore
+++ b/src/lib/dhcpsrv/tests/.gitignore
@@ -1 +1,2 @@
/libdhcpsrv_unittests
+/test_libraries.h
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 643fd635a2..28f804968c 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -33,7 +33,8 @@ if HAVE_GTEST
# to unexpected errors. For this reason, the --enable-static-link option is
# ignored for unit tests built here.
-lib_LTLIBRARIES = libco1.la libco2.la
+nodistdir=$(abs_top_builddir)/src/lib/dhcpsrv/tests
+nodist_LTLIBRARIES = libco1.la libco2.la
libco1_la_SOURCES = callout_library.cc
libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -52,6 +53,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
@@ -88,6 +90,7 @@ endif
libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index fda8d59c31..4330efd423 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -94,22 +94,56 @@ public:
duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
iaid_ = 42;
- // instantiate cfg_mgr
+ // Initialize a subnet and short address pool.
+ initSubnet(IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::20"));
+
+ initFqdn("", false, false);
+
+ factory_.create("type=memfile");
+ }
+
+ /// @brief Configures a subnet and adds one pool to it.
+ ///
+ /// This function removes existing v6 subnets before configuring
+ /// a new one.
+ ///
+ /// @param subnet Address of a subnet to be configured.
+ /// @param pool_start First address in the address pool.
+ /// @param pool_end Last address in the address pool.
+ void initSubnet(const IOAddress& subnet, const IOAddress& pool_start,
+ const IOAddress& pool_end) {
CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6();
+
+ subnet_ = Subnet6Ptr(new Subnet6(subnet, 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, pool_start, pool_end));
- // Configure normal address pool
- subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::10"),
- IOAddress("2001:db8:1::20")));
subnet_->addPool(pool_);
- // Configure PD pool
- pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 56, 64));
+ pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD, subnet, 56, 64));
subnet_->addPool(pd_pool_);
cfg_mgr.addSubnet6(subnet_);
- factory_.create("type=memfile");
+ }
+
+ /// @brief Initializes FQDN data for a test.
+ ///
+ /// The initialized values are used by the test fixture class members to
+ /// verify the correctness of a lease.
+ ///
+ /// @param hostname Hostname to be assigned to a lease.
+ /// @param fqdn_fwd Indicates whether or not to perform forward DNS update
+ /// for a lease.
+ /// @param fqdn_fwd Indicates whether or not to perform reverse DNS update
+ /// for a lease.
+ void initFqdn(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ hostname_ = hostname;
+ fqdn_fwd_ = fqdn_fwd;
+ fqdn_rev_ = fqdn_rev;
}
/// @brief attempts to convert leases collection to a single lease
@@ -151,10 +185,11 @@ public:
EXPECT_EQ(subnet_->getT1(), lease->t1_);
EXPECT_EQ(subnet_->getT2(), lease->t2_);
EXPECT_EQ(exp_pd_len, lease->prefixlen_);
- EXPECT_TRUE(false == lease->fqdn_fwd_);
- EXPECT_TRUE(false == lease->fqdn_rev_);
+ EXPECT_EQ(fqdn_fwd_, lease->fqdn_fwd_);
+ EXPECT_EQ(fqdn_rev_, lease->fqdn_rev_);
+ EXPECT_EQ(hostname_, lease->hostname_);
EXPECT_TRUE(*lease->duid_ == *duid_);
- // @todo: check cltt
+ /// @todo: check cltt
}
/// @brief Checks if specified address is increased properly
@@ -211,7 +246,7 @@ public:
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, hint, type, false, false,
- "", fake, CalloutHandlePtr())));
+ "", fake, CalloutHandlePtr(), old_leases_)));
// Check that we got a lease
EXPECT_TRUE(lease);
@@ -275,16 +310,16 @@ public:
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, requested, type, false, false, "", false,
- CalloutHandlePtr())));
+ CalloutHandlePtr(), old_leases_)));
// Check that we got a lease
ASSERT_TRUE(lease);
// Allocated address must be different
- EXPECT_NE(used_addr.toText(), lease->addr_.toText());
+ EXPECT_NE(used_addr, lease->addr_);
// We should NOT get what we asked for, because it is used already
- EXPECT_NE(requested.toText(), lease->addr_.toText());
+ EXPECT_NE(requested, lease->addr_);
// Do all checks on the lease
checkLease6(lease, type, expected_pd_len);
@@ -319,13 +354,13 @@ public:
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, hint, type, false,
- false, "", false, CalloutHandlePtr())));
+ false, "", false, CalloutHandlePtr(), old_leases_)));
// Check that we got a lease
ASSERT_TRUE(lease);
// We should NOT get what we asked for, because it is used already
- EXPECT_NE(hint.toText(), lease->addr_.toText());
+ EXPECT_NE(hint, lease->addr_);
// Do all checks on the lease
checkLease6(lease, type, expected_pd_len);
@@ -353,7 +388,14 @@ public:
Subnet6Ptr subnet_; ///< subnet6 (used in tests)
Pool6Ptr pool_; ///< NA pool belonging to subnet_
Pool6Ptr pd_pool_; ///< PD pool belonging to subnet_
+ std::string hostname_; ///< Hostname
+ bool fqdn_fwd_; ///< Perform forward update for a lease.
+ bool fqdn_rev_; ///< Perform reverse update for a lease.
LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+
+ /// @brief Collection of leases being replaced by newly allocated or renewed
+ /// leases.
+ Lease6Collection old_leases_;
};
/// @brief Used in Allocation Engine tests for IPv4
@@ -408,7 +450,7 @@ public:
EXPECT_TRUE(*lease->client_id_ == *clientid_);
}
EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
- // @todo: check cltt
+ /// @todo: check cltt
}
virtual ~AllocEngine4Test() {
@@ -476,7 +518,7 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
false);
// We should get what we asked for
- EXPECT_EQ(lease->addr_.toText(), "2001:db8:1::15");
+ EXPECT_EQ("2001:db8:1::15", lease->addr_.toText());
}
// This test checks if the address allocation with a hint that is in range,
@@ -521,13 +563,13 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(
Subnet6Ptr(), duid_, iaid_, IOAddress("::"), Lease::TYPE_NA,
- false, false, "", false, CalloutHandlePtr())));
+ false, false, "", false, CalloutHandlePtr(), old_leases_)));
ASSERT_FALSE(lease);
// Allocations without DUID are not allowed either
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
DuidPtr(), iaid_, IOAddress("::"), Lease::TYPE_NA, false,
- false, "", false, CalloutHandlePtr())));
+ false, "", false, CalloutHandlePtr(), old_leases_)));
ASSERT_FALSE(lease);
}
@@ -765,19 +807,18 @@ TEST_F(AllocEngine6Test, smallPool6) {
ASSERT_TRUE(engine);
IOAddress addr("2001:db8:1::ad");
- CfgMgr& cfg_mgr = CfgMgr::instance();
- cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
- // Create configuration similar to other tests, but with a single address pool
- subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
- subnet_->addPool(pool_);
- cfg_mgr.addSubnet6(subnet_);
+ // Create a subnet with a pool that has one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Initialize FQDN for a lease.
+ initFqdn("myhost.example.com", true, true);
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
- duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
- "", false, CalloutHandlePtr())));
+ duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, fqdn_fwd_,
+ fqdn_rev_, hostname_, false, CalloutHandlePtr(),
+ old_leases_)));
// Check that we got that single lease
ASSERT_TRUE(lease);
@@ -794,6 +835,11 @@ TEST_F(AllocEngine6Test, smallPool6) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease, from_mgr);
+
+ // This is a new lease allocation. The old lease corresponding to a newly
+ // allocated lease should be NULL.
+ ASSERT_EQ(1, old_leases_.size());
+ EXPECT_FALSE(old_leases_[0]);
}
// This test checks if all addresses in a pool are currently used, the attempt
@@ -826,8 +872,9 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
Lease6Ptr lease2;
EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
- "", false, CalloutHandlePtr())));
+ "", false, CalloutHandlePtr(), old_leases_)));
EXPECT_FALSE(lease2);
+
}
// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
@@ -837,14 +884,12 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
ASSERT_TRUE(engine);
IOAddress addr("2001:db8:1::ad");
- CfgMgr& cfg_mgr = CfgMgr::instance();
- cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
- // Create configuration similar to other tests, but with a single address pool
- subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
- subnet_->addPool(pool_);
- cfg_mgr.addSubnet6(subnet_);
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
// Just a different duid
DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
@@ -860,11 +905,12 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
// CASE 1: Asking for any address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
- duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false, "", true,
- CalloutHandlePtr())));
+ duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, fqdn_fwd_,
+ fqdn_rev_, hostname_, true, CalloutHandlePtr(),
+ old_leases_)));
// Check that we got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
// Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
checkLease6(lease, Lease::TYPE_NA, 128);
@@ -872,11 +918,11 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
// CASE 2: Asking specifically for this address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
- true, CalloutHandlePtr())));
+ true, CalloutHandlePtr(), old_leases_)));
// Check that we got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
}
// This test checks if an expired lease can be reused in REQUEST (actual allocation)
@@ -903,16 +949,32 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
501, 502, 503, 504, other_subnetid, 0));
lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// A client comes along, asking specifically for this address
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, addr, Lease::TYPE_NA, false, false, "",
- false, CalloutHandlePtr())));
+ false, CalloutHandlePtr(), old_leases_)));
// Check that he got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
+ // This reactivated lease should have updated FQDN data.
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+
+ // Check that the old lease has been returned.
+ Lease6Ptr old_lease = expectOneLease(old_leases_);
+ // It should at least have the same IPv6 address.
+ EXPECT_EQ(lease->addr_, old_lease->addr_);
+ // Check that it carries not updated FQDN data.
+ EXPECT_EQ("myhost.example.com.", old_lease->hostname_);
+ EXPECT_TRUE(old_lease->fqdn_fwd_);
+ EXPECT_TRUE(old_lease->fqdn_rev_);
// Check that the lease is indeed updated in LeaseMgr
Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
@@ -1075,10 +1137,10 @@ TEST_F(AllocEngine4Test, allocWithUsedHint4) {
ASSERT_TRUE(lease);
// Allocated address must be different
- EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+ EXPECT_NE(used->addr_, lease->addr_);
// We should NOT get what we asked for, because it is used already
- EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106");
+ EXPECT_NE("192.0.2.106", lease->addr_.toText());
// Do all checks on the lease
checkLease4(lease);
@@ -1115,7 +1177,7 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
EXPECT_FALSE(old_lease_);
// We should NOT get what we asked for, because it is used already
- EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
+ EXPECT_NE("10.1.1.1", lease->addr_.toText());
// Do all checks on the lease
checkLease4(lease);
@@ -1371,7 +1433,7 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
old_lease_);
// Check that we got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
// We are reusing expired lease, the old (expired) instance should be
// returned. The returned instance should be the same as the original
@@ -1384,13 +1446,13 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
// CASE 2: Asking specifically for this address
lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
- IOAddress(addr.toText()),
+ IOAddress(addr),
false, false, "",
true, CalloutHandlePtr(),
old_lease_);
// Check that we got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
// We are updating expired lease. The copy of the old lease should be
// returned and it should be equal to the original lease.
@@ -1425,14 +1487,14 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
// A client comes along, asking specifically for this address
lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
- IOAddress(addr.toText()),
+ IOAddress(addr),
false, true, "host.example.com.",
false, CalloutHandlePtr(),
old_lease_);
// Check that he got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
// Check that the lease is indeed updated in LeaseMgr
Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
@@ -1481,7 +1543,7 @@ TEST_F(AllocEngine4Test, renewLease4) {
callout_handle, false);
// Check that he got that single lease
ASSERT_TRUE(lease);
- EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ EXPECT_EQ(addr, lease->addr_);
// Check that the lease matches subnet_, hwaddr_,clientid_ parameters
checkLease4(lease);
@@ -1623,7 +1685,7 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
- "", false, callout_handle)));
+ "", false, callout_handle, old_leases_)));
// Check that we got a lease
ASSERT_TRUE(lease);
@@ -1694,7 +1756,7 @@ TEST_F(HookAllocEngine6Test, change_lease6_select) {
Lease6Ptr lease;
EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(subnet_,
duid_, iaid_, IOAddress("::"), Lease::TYPE_NA, false, false,
- "", false, callout_handle)));
+ "", false, callout_handle, old_leases_)));
// Check that we got a lease
ASSERT_TRUE(lease);
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 08ce768f62..e13cb320aa 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -538,7 +538,7 @@ TEST_F(CfgMgrTest, optionSpace4) {
cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
);
- // @todo decode if a duplicate vendor space is allowed.
+ /// @todo decode if a duplicate vendor space is allowed.
}
// This test verifies that new DHCPv6 option spaces can be added to
@@ -571,7 +571,7 @@ TEST_F(CfgMgrTest, optionSpace6) {
cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
);
- // @todo decide if a duplicate vendor space is allowed.
+ /// @todo decide if a duplicate vendor space is allowed.
}
// This test verifies that it is possible to specify interfaces that server
@@ -670,6 +670,48 @@ TEST_F(CfgMgrTest, echoClientId) {
EXPECT_TRUE(cfg_mgr.echoClientId());
}
+// This test checks the D2ClientMgr wrapper methods.
+TEST_F(CfgMgrTest, d2ClientConfig) {
+ // After CfgMgr construction, D2ClientMgr member should be initialized
+ // with a D2 configuration that is disabled.
+ // Verify we can Fetch the mgr.
+ D2ClientMgr d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ EXPECT_FALSE(d2_mgr.ddnsEnabled());
+
+ // Make sure the convenience method fetches the config correctly.
+ D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+ EXPECT_FALSE(original_config->getEnableUpdates());
+
+ // Verify that we cannot set the configuration to an empty pointer.
+ D2ClientConfigPtr new_cfg;
+ ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
+
+ // Create a new, enabled configuration.
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+
+ // Verify that we can assign a new, non-empty configuration.
+ ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg));
+
+ // Verify that we can fetch the newly assigned configuration.
+ D2ClientConfigPtr updated_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(updated_config);
+ EXPECT_TRUE(updated_config->getEnableUpdates());
+
+ // Make sure convenience method agrees with updated configuration.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Make sure the configuration we fetched is the one we assigned,
+ // and not the original configuration.
+ EXPECT_EQ(*new_cfg, *updated_config);
+ EXPECT_NE(*original_config, *updated_config);
+}
+
+
/// @todo Add unit-tests for testing:
/// - addActiveIface() with invalid interface name
/// - addActiveIface() with the same interface twice
diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
new file mode 100644
index 0000000000..8a8550fbe2
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
@@ -0,0 +1,798 @@
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcpsrv/d2_client.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+/// @brief Checks constructors and accessors of D2ClientConfig.
+TEST(D2ClientConfigTest, constructorsAndAccessors) {
+ D2ClientConfigPtr d2_client_config;
+
+ // Verify default constructor creates a disabled instance.
+ ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig()));
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ d2_client_config.reset();
+
+ bool enable_updates = true;
+ isc::asiolink::IOAddress server_ip("127.0.0.1");
+ size_t server_port = 477;
+ dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+ dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+ bool always_include_fqdn = true;
+ bool override_no_update = true;
+ bool override_client_update = true;
+ bool replace_client_name = true;
+ std::string generated_prefix = "the_prefix";
+ std::string qualifying_suffix = "the.suffix.";
+
+ // Verify that we can construct a valid, enabled instance.
+ ASSERT_NO_THROW(d2_client_config.reset(new
+ D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ ncr_protocol,
+ ncr_format,
+ always_include_fqdn,
+ override_no_update,
+ override_client_update,
+ replace_client_name,
+ generated_prefix,
+ qualifying_suffix)));
+
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the accessors return the expected values.
+ EXPECT_EQ(d2_client_config->getEnableUpdates(), enable_updates);
+
+ EXPECT_EQ(d2_client_config->getServerIp(), server_ip);
+ EXPECT_EQ(d2_client_config->getServerPort(), server_port);
+ EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
+ EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
+ EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
+ EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update);
+ EXPECT_EQ(d2_client_config->getOverrideClientUpdate(),
+ override_client_update);
+ EXPECT_EQ(d2_client_config->getReplaceClientName(), replace_client_name);
+ EXPECT_EQ(d2_client_config->getGeneratedPrefix(), generated_prefix);
+ EXPECT_EQ(d2_client_config->getQualifyingSuffix(), qualifying_suffix);
+
+ // Verify that toText called by << operator doesn't bomb.
+ ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
+ *d2_client_config << std::endl);
+
+ // Verify that constructor does not allow use of NCR_TCP.
+ /// @todo obviously this becomes invalid once TCP is supported.
+ ASSERT_THROW(d2_client_config.reset(new
+ D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ dhcp_ddns::NCR_TCP,
+ ncr_format,
+ always_include_fqdn,
+ override_no_update,
+ override_client_update,
+ replace_client_name,
+ generated_prefix,
+ qualifying_suffix)),
+ D2ClientError);
+
+ /// @todo if additional validation is added to ctor, this test needs to
+ /// expand accordingly.
+}
+
+/// @brief Tests the equality and inequality operators of D2ClientConfig.
+TEST(D2ClientConfigTest, equalityOperator) {
+ D2ClientConfigPtr ref_config;
+ D2ClientConfigPtr test_config;
+
+ isc::asiolink::IOAddress ref_address("127.0.0.1");
+ isc::asiolink::IOAddress test_address("127.0.0.2");
+
+ // Create an instance to use as a reference.
+ ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(ref_config);
+
+ // Check a configuration that is identical to reference configuration.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_TRUE(*ref_config == *test_config);
+ EXPECT_FALSE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by enable flag.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by server ip.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ test_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by server port.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 333,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by always_include_fqdn.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, true, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by override_no_update.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, false, true, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by override_client_update.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, false, true,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by replace_client_name.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by generated_prefix.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "bogus", "suf-fix")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by qualifying_suffix.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "bogus")));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+}
+
+/// @brief Checks the D2ClientMgr constructor.
+TEST(D2ClientMgr, constructor) {
+ D2ClientMgrPtr d2_client_mgr;
+
+ // Verify we can construct with the default constructor.
+ ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+
+ // After construction, D2 configuration should be disabled.
+ // Fetch it and verify this is the case.
+ D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+ EXPECT_FALSE(original_config->getEnableUpdates());
+
+ // Make sure convenience method agrees.
+ EXPECT_FALSE(d2_client_mgr->ddnsEnabled());
+}
+
+/// @brief Checks passing the D2ClientMgr a valid D2 client configuration.
+/// @todo Once NameChangeSender is integrated, this test needs to expand, and
+/// additional scenario tests will need to be written.
+TEST(D2ClientMgr, validConfig) {
+ D2ClientMgrPtr d2_client_mgr;
+
+ // Construct the manager and fetch its initial configuration.
+ ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+ D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+
+ // Verify that we cannot set the config to an empty pointer.
+ D2ClientConfigPtr new_cfg;
+ ASSERT_THROW(d2_client_mgr->setD2ClientConfig(new_cfg), D2ClientError);
+
+ // Create a new, enabled config.
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ true, true, true, true,
+ "pre-fix", "suf-fix")));
+
+ // Verify that we can assign a new, non-empty configuration.
+ ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg));
+
+ // Verify that we can fetch the newly assigned configuration.
+ D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(updated_config);
+ EXPECT_TRUE(updated_config->getEnableUpdates());
+
+ // Make sure convenience method agrees with the updated configuration.
+ EXPECT_TRUE(d2_client_mgr->ddnsEnabled());
+
+ // Make sure the configuration we fetched is the one we assigned,
+ // and not the original configuration.
+ EXPECT_EQ(*new_cfg, *updated_config);
+ EXPECT_NE(*original_config, *updated_config);
+}
+
+
+/// @brief Tests that analyzeFqdn detects invalid combination of both the
+/// client S and N flags set to true.
+TEST(D2ClientMgr, analyzeFqdnInvalidCombination) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create disabled configuration.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig()));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_FALSE(mgr.ddnsEnabled());
+
+ // client S=1 N=1 is invalid. analyzeFqdn should throw.
+ ASSERT_THROW(mgr.analyzeFqdn(true, true, server_s, server_n),
+ isc::BadValue);
+
+ // Create enabled configuration with all controls off (no overrides).
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+
+ // client S=1 N=1 is invalid. analyzeFqdn should throw.
+ ASSERT_THROW(mgr.analyzeFqdn(true, true, server_s, server_n),
+ isc::BadValue);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and all overrides are off.
+TEST(D2ClientMgr, analyzeFqdnEnabledNoOverrides) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with all controls off (no overrides).
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_FALSE(cfg->getOverrideClientUpdate());
+ ASSERT_FALSE(cfg->getOverrideNoUpdate());
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 1 (server doing no updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 0 (server is not forward updates)
+ // and server N should be 1 (server is not doing any updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-no-update is on.
+TEST(D2ClientMgr, analyzeFqdnEnabledOverrideNoUpdate) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with OVERRIDE_NO_UPDATE on.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, true, false, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_TRUE(cfg->getOverrideNoUpdate());
+ ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 1 (server is not doing any updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server is doing updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-client-update is on.
+TEST(D2ClientMgr, analyzeFqdnEnabledOverrideClientUpdate) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with OVERRIDE_CLIENT_UPDATE on.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, true, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_FALSE(cfg->getOverrideNoUpdate());
+ ASSERT_TRUE(cfg->getOverrideClientUpdate());
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 0 (server is not forward updates)
+ // and server N should be 1 (server is not doing any updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option4ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlag tests.
+TEST(D2ClientMgr, adjustFqdnFlagsV4) {
+ D2ClientMgr mgr;
+ Option4ClientFqdnPtr request;
+ Option4ClientFqdnPtr response;
+
+ // Create enabled configuration and override-no-update on.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, true, false, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_TRUE(cfg->getOverrideNoUpdate());
+ ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 1 (server doing no updates)
+ // and server O should be 0
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and server O should be 0
+ request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // client S=0 N=1 means client wants no one to do updates
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and O should be 1 (overriding client S)
+ request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response);
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_O));
+}
+
+/// @brief Tests the qualifyName method's ability to construct FQDNs
+TEST(D2ClientMgr, qualifyName) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, true, false,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+ // Verify that the qualifying suffix gets appended with trailing dot added.
+ std::string partial_name = "somehost";
+ std::string qualified_name = mgr.qualifyName(partial_name);
+ EXPECT_EQ("somehost.suffix.com.", qualified_name);
+
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, true, false,
+ "prefix", "hasdot.com.")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+ // Verify that the qualifying suffix gets appended without dot added.
+ qualified_name = mgr.qualifyName(partial_name);
+ EXPECT_EQ("somehost.hasdot.com.", qualified_name);
+}
+
+
+/// @brief Tests the generateFdqn method's ability to construct FQDNs
+TEST(D2ClientMgr, generateFqdn) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, true, false,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+ // Verify that it works with an IPv4 address.
+ asiolink::IOAddress v4address("192.0.2.75");
+ EXPECT_EQ("prefix-192-0-2-75.suffix.com.", mgr.generateFqdn(v4address));
+
+ // Verify that it works with an IPv6 address.
+ asiolink::IOAddress v6address("2001:db8::2");
+ EXPECT_EQ("prefix-2001-db8--2.suffix.com.", mgr.generateFqdn(v6address));
+
+ // Create a disabled config.
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig()));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+
+ // Verify names generate properly with a disabled configuration.
+ EXPECT_EQ("myhost-192-0-2-75.example.com.", mgr.generateFqdn(v4address));
+ EXPECT_EQ("myhost-2001-db8--2.example.com.", mgr.generateFqdn(v6address));
+}
+
+/// @brief Tests adjustDomainName template method with Option4ClientFqdn
+TEST(D2ClientMgr, adjustDomainNameV4) {
+ D2ClientMgr mgr;
+ Option4ClientFqdnPtr request;
+ Option4ClientFqdnPtr response;
+
+ // Create enabled configuration.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, false,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_FALSE(cfg->getReplaceClientName());
+
+ // replace-client-name is false, client passes in empty fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+ // replace-client-name is false, client passes in a partial fqdn
+ // response should contain client's name plus the qualifying suffix.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("myhost.suffix.com.", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, response->getDomainNameType());
+
+
+ // replace-client-name is false, client passes in a full fqdn
+ // response domain should not be altered.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL));
+ response.reset(new Option4ClientFqdn(*request));
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("myhost.example.com.", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, response->getDomainNameType());
+
+ // Create enabled configuration.
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, true,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(cfg->getReplaceClientName());
+
+ // replace-client-name is true, client passes in empty fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+ // replace-client-name is true, client passes in a partial fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+ // replace-client-name is true, client passes in a full fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com.",
+ Option4ClientFqdn::FULL));
+ response.reset(new Option4ClientFqdn(*request));
+ mgr.adjustDomainName<Option4ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, response->getDomainNameType());
+}
+
+/// @brief Tests adjustDomainName template method with Option6ClientFqdn
+TEST(D2ClientMgr, adjustDomainNameV6) {
+ D2ClientMgr mgr;
+ Option6ClientFqdnPtr request;
+ Option6ClientFqdnPtr response;
+
+ // Create enabled configuration.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, false,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_FALSE(cfg->getReplaceClientName());
+
+ // replace-client-name is false, client passes in empty fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+ // replace-client-name is false, client passes in a partial fqdn
+ // response should contain client's name plus the qualifying suffix.
+ request.reset(new Option6ClientFqdn(0, "myhost",
+ Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("myhost.suffix.com.", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, response->getDomainNameType());
+
+
+ // replace-client-name is false, client passes in a full fqdn
+ // response domain should not be altered.
+ request.reset(new Option6ClientFqdn(0, "myhost.example.com.",
+ Option6ClientFqdn::FULL));
+ response.reset(new Option6ClientFqdn(*request));
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("myhost.example.com.", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, response->getDomainNameType());
+
+ // Create enabled configuration.
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, false, false, true,
+ "prefix", "suffix.com")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(cfg->getReplaceClientName());
+
+ // replace-client-name is true, client passes in empty fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+ // replace-client-name is true, client passes in a partial fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option6ClientFqdn(0, "myhost",
+ Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+
+
+ // replace-client-name is true, client passes in a full fqdn
+ // reponse domain should be empty/partial.
+ request.reset(new Option6ClientFqdn(0, "myhost.example.com.",
+ Option6ClientFqdn::FULL));
+ response.reset(new Option6ClientFqdn(*request));
+ mgr.adjustDomainName<Option6ClientFqdn>(*request, *response);
+ EXPECT_EQ("", response->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, response->getDomainNameType());
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option6ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlags tests.
+TEST(D2ClientMgr, adjustFqdnFlagsV6) {
+ D2ClientMgr mgr;
+ Option6ClientFqdnPtr request;
+ Option6ClientFqdnPtr response;
+
+ // Create enabled configuration and override-no-update on.
+ D2ClientConfigPtr cfg;
+ ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+ false, true, false, false,
+ "pre-fix", "suf-fix")));
+ ASSERT_NO_THROW(mgr.setD2ClientConfig(cfg));
+ ASSERT_TRUE(mgr.ddnsEnabled());
+ ASSERT_TRUE(cfg->getOverrideNoUpdate());
+ ASSERT_FALSE(cfg->getOverrideClientUpdate());
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 1 (server doing no updates)
+ // and server O should be 0
+ request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and server O should be 0
+ request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // client S=0 N=1 means client wants no one to do updates
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and O should be 1 (overriding client S)
+ request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response);
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_O));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index 928be23ea3..78e3442273 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -367,8 +367,8 @@ public:
///
/// Note that the method currently it only supports option-defs, option-data
/// and hooks-libraries.
- ///
- /// @param config_id is the name of the configuration element.
+ ///
+ /// @param config_id is the name of the configuration element.
///
/// @return returns a shared pointer to DhcpConfigParser.
///
@@ -376,20 +376,21 @@ public:
ParserPtr createConfigParser(const std::string& config_id) {
ParserPtr parser;
if (config_id.compare("option-data") == 0) {
- parser.reset(new OptionDataListParser(config_id,
- parser_context_->options_,
+ parser.reset(new OptionDataListParser(config_id,
+ parser_context_->options_,
parser_context_,
UtestOptionDataParser::factory));
} else if (config_id.compare("option-def") == 0) {
- parser.reset(new OptionDefListParser(config_id,
+ parser.reset(new OptionDefListParser(config_id,
parser_context_->option_defs_));
} else if (config_id.compare("hooks-libraries") == 0) {
parser.reset(new HooksLibrariesParser(config_id));
hooks_libraries_parser_ =
boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
-
+ } else if (config_id.compare("dhcp-ddns") == 0) {
+ parser.reset(new D2ClientConfigParser(config_id));
} else {
isc_throw(NotImplemented,
"Parser error: configuration parameter not supported: "
@@ -399,8 +400,8 @@ public:
return (parser);
}
- /// @brief Convenience method for parsing a configuration
- ///
+ /// @brief Convenience method for parsing a configuration
+ ///
/// Given a configuration string, convert it into Elements
/// and parse them.
/// @param config is the configuration string to parse
@@ -491,6 +492,10 @@ public:
// Ensure no hooks libraries are loaded.
HooksManager::unloadLibraries();
+
+ // Set it to minimal, disabled config
+ D2ClientConfigPtr tmp(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(tmp);
}
/// @brief Parsers used in the parsing of the configuration
@@ -566,7 +571,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
" \"name\": \"foo\","
" \"space\": \"isc\","
" \"code\": 100,"
- " \"data\": \"192.168.2.1\","
+ " \"data\": \"192.0.2.0\","
" \"csv-format\": True"
" } ]"
"}";
@@ -581,7 +586,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
// Verify that the option definition is correct.
std::string val = "type=100, len=4, data fields:\n "
- " #0 192.168.2.1 ( ipv4-address ) \n";
+ " #0 192.0.2.0 ( ipv4-address ) \n";
EXPECT_EQ(val, opt_ptr->toText());
}
@@ -677,7 +682,7 @@ TEST_F(ParseConfigTest, validHooksLibrariesTest) {
// Check with a set of libraries, some of which are invalid.
TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
- // @todo Initialize global library context to null
+ /// @todo Initialize global library context to null
// Configuration string. This contains an invalid library which should
// trigger an error in the "build" stage.
@@ -703,6 +708,252 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
"Error text returned from parse failure is " << error_text_;
}
+/// @brief Checks that a valid, enabled D2 client configuration works correctly.
+TEST_F(ParseConfigTest, validD2Config) {
+
+ // Configuration string containing valid values.
+ std::string config_str =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 3432, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config_str);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+ D2ClientConfigPtr d2_client_config;
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are as expected.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("192.0.2.0", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(3432, d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+ EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
+ EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+ EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+ EXPECT_TRUE(d2_client_config->getReplaceClientName());
+ EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+ EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+
+ // Another valid Configuration string.
+ // This one is disabled, has IPV6 server ip, control flags false,
+ // empty prefix/suffix
+ std::string config_str2 =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : false, "
+ " \"server-ip\" : \"2001:db8::\", "
+ " \"server-port\" : 43567, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : false, "
+ " \"override-no-update\" : false, "
+ " \"override-client-update\" : false, "
+ " \"replace-client-name\" : false, "
+ " \"generated-prefix\" : \"\", "
+ " \"qualifying-suffix\" : \"\" "
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ rcode = parseConfiguration(config_str2);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is disabled and we can fetch the configuration.
+ EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are as expected.
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("2001:db8::", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(43567, d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+ EXPECT_FALSE(d2_client_config->getAlwaysIncludeFqdn());
+ EXPECT_FALSE(d2_client_config->getOverrideNoUpdate());
+ EXPECT_FALSE(d2_client_config->getOverrideClientUpdate());
+ EXPECT_FALSE(d2_client_config->getReplaceClientName());
+ EXPECT_EQ("", d2_client_config->getGeneratedPrefix());
+ EXPECT_EQ("", d2_client_config->getQualifyingSuffix());
+}
+
+/// @brief Checks that D2 client can be configured with enable flag of
+/// false only.
+TEST_F(ParseConfigTest, validDisabledD2Config) {
+
+ // Configuration string. This contains a set of valid libraries.
+ std::string config_str =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : false"
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config_str);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is disabled.
+ EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ // Make sure fetched config agrees.
+ D2ClientConfigPtr d2_client_config;
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ EXPECT_TRUE(d2_client_config);
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+}
+
+/// @brief Check various invalid D2 client configurations.
+TEST_F(ParseConfigTest, invalidD2Config) {
+ std::string invalid_configs[] = {
+ // only the enable flag of true
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true"
+ " }"
+ "}",
+ // Missing server ip value
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ //" \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // Invalid server ip value
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"x192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // Unknown protocol
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"Bogus\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // Unsupported protocol
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"TCP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // Unknown format
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"Bogus\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // Missig Port
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ // " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"always-include-fqdn\" : true, "
+ " \"override-no-update\" : true, "
+ " \"override-client-update\" : true, "
+ " \"replace-client-name\" : true, "
+ " \"generated-prefix\" : \"test.prefix\", "
+ " \"qualifying-suffix\" : \"test.suffix.\" "
+ " }"
+ "}",
+ // stop
+ ""
+ };
+
+ // Fetch the original config.
+ D2ClientConfigPtr original_config;
+ ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig());
+
+ // Iterate through the invalid configuration strings, attempting to
+ // parse each one. They should fail to parse, but fail gracefully.
+ D2ClientConfigPtr current_config;
+ int i = 0;
+ while (!invalid_configs[i].empty()) {
+ // Verify that the configuration string parses without throwing.
+ int rcode = parseConfiguration(invalid_configs[i]);
+
+ // Verify that parse result indicates a parsing error.
+ ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i
+ << " should not have passed!";
+
+ // Verify that the "official" config still matches the original config.
+ ASSERT_NO_THROW(current_config =
+ CfgMgr::instance().getD2ClientConfig());
+ EXPECT_EQ(*original_config, *current_config);
+ ++i;
+ }
+}
+
/// @brief DHCP Configuration Parser Context test fixture.
class ParserContextTest : public ::testing::Test {
public:
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index ab5a8b2162..f8a4aed3b5 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -251,17 +251,6 @@ public:
namespace {
-/// Hardware address used by different tests.
-const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
-/// Client id used by different tests.
-const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
-/// Valid lifetime value used by different tests.
-const uint32_t VALID_LIFETIME = 500;
-/// Subnet ID used by different tests.
-const uint32_t SUBNET_ID = 42;
-/// IAID value used by different tests.
-const uint32_t IAID = 7;
-
/// @brief getParameter test
///
/// This test checks if the LeaseMgr can be instantiated and that it
@@ -320,616 +309,6 @@ TEST_F(LeaseMgrTest, getLease6) {
// are purely virtual, so we would only call ConcreteLeaseMgr methods.
// Those methods are just stubs that do not return anything.
-/// @brief Lease4 Constructor Test
-///
-/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
-// This test checks if the Lease4 structure can be instantiated correctly
-TEST(Lease4, constructor) {
-
- // Random values for the tests
- const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
- std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
-
- const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
- std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
- ClientId clientid(clientid_vec);
-
- // ...and a time
- const time_t current_time = time(NULL);
-
- // Other random constants.
- const uint32_t SUBNET_ID = 42;
- const uint32_t VALID_LIFETIME = 500;
-
- // We want to check that various addresses work, so let's iterate over
- // these.
- const uint32_t ADDRESS[] = {
- 0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
- };
-
- for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
-
- // Create the lease
- Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0,
- current_time, SUBNET_ID, true, true,
- "hostname.example.com.");
-
- EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
- EXPECT_EQ(0, lease.ext_);
- EXPECT_TRUE(hwaddr == lease.hwaddr_);
- EXPECT_TRUE(clientid == *lease.client_id_);
- EXPECT_EQ(0, lease.t1_);
- EXPECT_EQ(0, lease.t2_);
- EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
- EXPECT_EQ(current_time, lease.cltt_);
- EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
- EXPECT_FALSE(lease.fixed_);
- EXPECT_EQ("hostname.example.com.", lease.hostname_);
- EXPECT_TRUE(lease.fqdn_fwd_);
- EXPECT_TRUE(lease.fqdn_rev_);
- EXPECT_TRUE(lease.comments_.empty());
- }
-}
-
-// This test verfies that copy constructor copies Lease4 fields correctly.
-TEST(Lease4, copyConstructor) {
-
- // Random values for the tests
- const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
- std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
-
- const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
- std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
- ClientId clientid(clientid_vec);
-
- // ...and a time
- const time_t current_time = time(NULL);
-
- // Other random constants.
- const uint32_t SUBNET_ID = 42;
- const uint32_t VALID_LIFETIME = 500;
-
- // Create the lease
- Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
- SUBNET_ID);
-
- // Use copy constructor to copy the lease.
- Lease4 copied_lease(lease);
-
- // Both leases should be now equal. When doing this check we assume that
- // the equality operator works correctly.
- EXPECT_TRUE(lease == copied_lease);
- // Client IDs are equal, but they should be in two distinct pointers.
- EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
-}
-
-// This test verfies that the assignment operator copies all Lease4 fields
-// correctly.
-TEST(Lease4, operatorAssign) {
-
- // Random values for the tests
- const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
- std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
-
- const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
- std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
- ClientId clientid(clientid_vec);
-
- // ...and a time
- const time_t current_time = time(NULL);
-
- // Other random constants.
- const uint32_t SUBNET_ID = 42;
- const uint32_t VALID_LIFETIME = 500;
-
- // Create the lease
- Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
- SUBNET_ID);
-
- // Use assignment operator to assign the lease.
- Lease4 copied_lease = lease;
-
- // Both leases should be now equal. When doing this check we assume that
- // the equality operator works correctly.
- EXPECT_TRUE(lease == copied_lease);
- // Client IDs are equal, but they should be in two distinct pointers.
- EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
-}
-
-// This test verifies that the matches() returns true if two leases differ
-// by values other than address, HW address, Client ID and ext_.
-TEST(Lease4, matches) {
- // Create two leases which share the same address, HW address, client id
- // and ext_ value.
- const time_t current_time = time(NULL);
- Lease4 lease1(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
- sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
- SUBNET_ID);
- lease1.hostname_ = "lease1.example.com.";
- lease1.fqdn_fwd_ = true;
- lease1.fqdn_rev_ = true;
- Lease4 lease2(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
- sizeof(CLIENTID), VALID_LIFETIME + 10, current_time - 10,
- 100, 200, SUBNET_ID);
- lease2.hostname_ = "lease2.example.com.";
- lease2.fqdn_fwd_ = false;
- lease2.fqdn_rev_ = true;
-
- // Leases should match.
- EXPECT_TRUE(lease1.matches(lease2));
- EXPECT_TRUE(lease2.matches(lease1));
-
- // Change address, leases should not match anymore.
- lease1.addr_ = IOAddress("192.0.2.4");
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.addr_ = lease2.addr_;
-
- // Change HW address, leases should not match.
- lease1.hwaddr_[1] += 1;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.hwaddr_ = lease2.hwaddr_;
-
- // Chanage client id, leases should not match.
- std::vector<uint8_t> client_id = lease1.client_id_->getClientId();
- client_id[1] += 1;
- lease1.client_id_.reset(new ClientId(client_id));
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.client_id_ = lease2.client_id_;
-
- // Change ext_, leases should not match.
- lease1.ext_ += 1;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.ext_ = lease2.ext_;
-}
-
-/// @brief Lease4 Equality Test
-///
-/// Checks that the operator==() correctly compares two leases for equality.
-/// As operator!=() is also defined for this class, every check on operator==()
-/// is followed by the reverse check on operator!=().
-TEST(Lease4, operatorEquals) {
-
- // Random values for the tests
- const uint32_t ADDRESS = 0x01020304;
- const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
- std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
- const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
- std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
- ClientId clientid(clientid_vec);
- const time_t current_time = time(NULL);
- const uint32_t SUBNET_ID = 42;
- const uint32_t VALID_LIFETIME = 500;
-
- // Check when the leases are equal.
- Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0,
- 0, SUBNET_ID);
- Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
- SUBNET_ID);
- EXPECT_TRUE(lease1 == lease2);
- EXPECT_FALSE(lease1 != lease2);
-
- // Now vary individual fields in a lease and check that the leases compare
- // not equal in every case.
- lease1.addr_ = IOAddress(ADDRESS + 1);
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.addr_ = lease2.addr_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.ext_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.ext_ = lease2.ext_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.hwaddr_[0];
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.hwaddr_ = lease2.hwaddr_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++clientid_vec[0];
- lease1.client_id_.reset(new ClientId(clientid_vec));
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- --clientid_vec[0];
- lease1.client_id_.reset(new ClientId(clientid_vec));
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.t1_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.t1_ = lease2.t1_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.t2_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.t2_ = lease2.t2_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.valid_lft_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.valid_lft_ = lease2.valid_lft_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.cltt_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.cltt_ = lease2.cltt_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.subnet_id_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.subnet_id_ = lease2.subnet_id_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fixed_ = !lease1.fixed_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fixed_ = lease2.fixed_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.hostname_ += string("Something random");
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.hostname_ = lease2.hostname_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fqdn_rev_ = !lease1.fqdn_rev_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fqdn_rev_ = lease2.fqdn_rev_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.comments_ += string("Something random");
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.comments_ = lease2.comments_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-}
-
-
-
-// Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
-// This test checks if the Lease6 structure can be instantiated correctly
-TEST(Lease6, Lease6ConstructorDefault) {
-
- // check a variety of addresses with different bits set.
- const char* ADDRESS[] = {
- "::", "::1", "2001:db8:1::456",
- "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
- "8000::", "8000::1",
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
- };
-
- // Other values
- uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- DuidPtr duid(new DUID(llt, sizeof(llt)));
- uint32_t iaid = 7; // Just a number
- SubnetID subnet_id = 8; // Just another number
-
- for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
- IOAddress addr(ADDRESS[i]);
- Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
- duid, iaid, 100, 200, 50, 80,
- subnet_id));
-
- EXPECT_TRUE(lease->addr_ == addr);
- EXPECT_TRUE(*lease->duid_ == *duid);
- EXPECT_TRUE(lease->iaid_ == iaid);
- EXPECT_TRUE(lease->subnet_id_ == subnet_id);
- EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
- EXPECT_TRUE(lease->preferred_lft_ == 100);
- EXPECT_TRUE(lease->valid_lft_ == 200);
- EXPECT_TRUE(lease->t1_ == 50);
- EXPECT_TRUE(lease->t2_ == 80);
- EXPECT_FALSE(lease->fqdn_fwd_);
- EXPECT_FALSE(lease->fqdn_rev_);
- EXPECT_TRUE(lease->hostname_.empty());
-
- }
-
- // Lease6 must be instantiated with a DUID, not with NULL pointer
- IOAddress addr(ADDRESS[0]);
- Lease6Ptr lease2;
- EXPECT_THROW(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
- DuidPtr(), iaid, 100, 200, 50, 80,
- subnet_id)), InvalidOperation);
-}
-
-// This test verifies that the Lease6 constructor which accepts FQDN data,
-// sets the data correctly for the lease.
-TEST(Lease6, Lease6ConstructorWithFQDN) {
-
- // check a variety of addresses with different bits set.
- const char* ADDRESS[] = {
- "::", "::1", "2001:db8:1::456",
- "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
- "8000::", "8000::1",
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
- };
-
- // Other values
- uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- DuidPtr duid(new DUID(llt, sizeof(llt)));
- uint32_t iaid = 7; // Just a number
- SubnetID subnet_id = 8; // Just another number
-
- for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
- IOAddress addr(ADDRESS[i]);
- Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
- duid, iaid, 100, 200, 50, 80, subnet_id,
- true, true, "host.example.com."));
-
- EXPECT_TRUE(lease->addr_ == addr);
- EXPECT_TRUE(*lease->duid_ == *duid);
- EXPECT_TRUE(lease->iaid_ == iaid);
- EXPECT_TRUE(lease->subnet_id_ == subnet_id);
- EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
- EXPECT_TRUE(lease->preferred_lft_ == 100);
- EXPECT_TRUE(lease->valid_lft_ == 200);
- EXPECT_TRUE(lease->t1_ == 50);
- EXPECT_TRUE(lease->t2_ == 80);
- EXPECT_TRUE(lease->fqdn_fwd_);
- EXPECT_TRUE(lease->fqdn_rev_);
- EXPECT_EQ("host.example.com.", lease->hostname_);
- }
- // Lease6 must be instantiated with a DUID, not with NULL pointer
- IOAddress addr(ADDRESS[0]);
- Lease6Ptr lease2;
- EXPECT_THROW(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
- DuidPtr(), iaid, 100, 200, 50, 80,
- subnet_id)), InvalidOperation);
-}
-
-// This test verifies that the matches() function returns true if two leases
-// differ by values other than address, type, prefix length, IAID and DUID.
-TEST(Lease6, matches) {
-
- // Create two matching leases.
- uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- DuidPtr duid(new DUID(llt, sizeof(llt)));
-
- Lease6 lease1(Lease6::TYPE_NA, IOAddress("2001:db8:1::1"), duid,
- IAID, 100, 200, 50, 80,
- SUBNET_ID);
- lease1.hostname_ = "lease1.example.com.";
- lease1.fqdn_fwd_ = true;
- lease1.fqdn_rev_ = true;
- Lease6 lease2(Lease6::TYPE_NA, IOAddress("2001:db8:1::1"), duid,
- IAID, 200, 300, 90, 70,
- SUBNET_ID);
- lease2.hostname_ = "lease1.example.com.";
- lease2.fqdn_fwd_ = false;
- lease2.fqdn_rev_ = true;
-
- EXPECT_TRUE(lease1.matches(lease2));
-
- // Modify each value used to match both leases, and make sure that
- // leases don't match.
-
- // Modify address.
- lease1.addr_ = IOAddress("2001:db8:1::2");
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.addr_ = lease2.addr_;
-
- // Modify lease type.
- lease1.type_ = Lease6::TYPE_TA;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.type_ = lease2.type_;
-
- // Modify prefix length.
- lease1.prefixlen_ += 1;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.prefixlen_ = lease2.prefixlen_;
-
- // Modify IAID.
- lease1.iaid_ += 1;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.iaid_ = lease2.iaid_;
-
- // Modify DUID.
- llt[1] += 1;
- duid.reset(new DUID(llt, sizeof(llt)));
- lease1.duid_ = duid;
- EXPECT_FALSE(lease1.matches(lease2));
- lease1.duid_ = lease2.duid_;
-}
-
-/// @brief Lease6 Equality Test
-///
-/// Checks that the operator==() correctly compares two leases for equality.
-/// As operator!=() is also defined for this class, every check on operator==()
-/// is followed by the reverse check on operator!=().
-TEST(Lease6, OperatorEquals) {
-
- // check a variety of addresses with different bits set.
- const IOAddress addr("2001:db8:1::456");
- uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
- uint32_t iaid = 7; // just a number
- SubnetID subnet_id = 8; // just another number
-
- // Check for equality.
- Lease6 lease1(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
- subnet_id);
- Lease6 lease2(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
- subnet_id);
-
- // cltt_ constructs with time(NULL), make sure they are always equal
- lease1.cltt_ = lease2.cltt_;
-
- EXPECT_TRUE(lease1 == lease2);
- EXPECT_FALSE(lease1 != lease2);
-
- // Go through and alter all the fields one by one
-
- lease1.addr_ = IOAddress("::1");
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.addr_ = lease2.addr_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.type_ = Lease::TYPE_PD;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.type_ = lease2.type_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.prefixlen_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.prefixlen_ = lease2.prefixlen_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.iaid_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.iaid_ = lease2.iaid_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++duid_array[0];
- lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- --duid_array[0];
- lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.preferred_lft_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.preferred_lft_ = lease2.preferred_lft_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.valid_lft_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.valid_lft_ = lease2.valid_lft_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.t1_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.t1_ = lease2.t1_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.t2_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.t2_ = lease2.t2_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.cltt_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.cltt_ = lease2.cltt_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- ++lease1.subnet_id_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.subnet_id_ = lease2.subnet_id_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fixed_ = !lease1.fixed_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fixed_ = lease2.fixed_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.hostname_ += string("Something random");
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.hostname_ = lease2.hostname_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.fqdn_rev_ = !lease1.fqdn_rev_;
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.fqdn_rev_ = lease2.fqdn_rev_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-
- lease1.comments_ += string("Something random");
- EXPECT_FALSE(lease1 == lease2);
- EXPECT_TRUE(lease1 != lease2);
- lease1.comments_ = lease2.comments_;
- EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
- EXPECT_FALSE(lease1 != lease2); // ... leases equal
-}
-
-// Checks if lease expiration is calculated properly
-TEST(Lease6, Lease6Expired) {
- const IOAddress addr("2001:db8:1::456");
- const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
- const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
- const uint32_t iaid = 7; // Just a number
- const SubnetID subnet_id = 8; // Just another number
- Lease6 lease(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
- subnet_id);
-
- // Case 1: a second before expiration
- lease.cltt_ = time(NULL) - 100;
- lease.valid_lft_ = 101;
- EXPECT_FALSE(lease.expired());
-
- // Case 2: the lease will expire after this second is concluded
- lease.cltt_ = time(NULL) - 101;
- EXPECT_FALSE(lease.expired());
-
- // Case 3: the lease is expired
- lease.cltt_ = time(NULL) - 102;
- EXPECT_TRUE(lease.expired());
-}
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_unittest.cc b/src/lib/dhcpsrv/tests/lease_unittest.cc
index 6f57f30e39..4e7451f2d1 100644
--- a/src/lib/dhcpsrv/tests/lease_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -13,23 +13,342 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
+#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcpsrv/lease.h>
#include <gtest/gtest.h>
#include <vector>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
namespace {
-// @todo Currently this file contains tests for new functions which return DUID
-// or client identifier. Other tests for Lease objects must be implemented.
-// See http://bind10.isc.org/ticket/3240.
+/// Hardware address used by different tests.
+const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+/// Client id used by different tests.
+const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+/// Valid lifetime value used by different tests.
+const uint32_t VALID_LIFETIME = 500;
+/// Subnet ID used by different tests.
+const uint32_t SUBNET_ID = 42;
+/// IAID value used by different tests.
+const uint32_t IAID = 7;
+
+/// @brief Creates an instance of the lease with certain FQDN data.
+///
+/// @param hostname Hostname.
+/// @param fqdn_fwd Forward FQDN update setting for a created lease.
+/// @param fqdn_rev Reverse FQDN update setting for a created lease.
+///
+/// @return Instance of the created lease.
+Lease4 createLease4(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ Lease4 lease;
+ lease.hostname_ = hostname;
+ lease.fqdn_fwd_ = fqdn_fwd;
+ lease.fqdn_rev_ = fqdn_rev;
+ return (lease);
+}
+
+/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease4 structure can be instantiated correctly
+TEST(Lease4, constructor) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // We want to check that various addresses work, so let's iterate over
+ // these.
+ const uint32_t ADDRESS[] = {
+ 0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
+ };
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+
+ // Create the lease
+ Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0,
+ current_time, SUBNET_ID, true, true,
+ "hostname.example.com.");
+
+ EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
+ EXPECT_EQ(0, lease.ext_);
+ EXPECT_TRUE(hwaddr == lease.hwaddr_);
+ EXPECT_TRUE(clientid == *lease.client_id_);
+ EXPECT_EQ(0, lease.t1_);
+ EXPECT_EQ(0, lease.t2_);
+ EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
+ EXPECT_EQ(current_time, lease.cltt_);
+ EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
+ EXPECT_FALSE(lease.fixed_);
+ EXPECT_EQ("hostname.example.com.", lease.hostname_);
+ EXPECT_TRUE(lease.fqdn_fwd_);
+ EXPECT_TRUE(lease.fqdn_rev_);
+ EXPECT_TRUE(lease.comments_.empty());
+ }
+}
+
+// This test verfies that copy constructor copies Lease4 fields correctly.
+TEST(Lease4, copyConstructor) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Create the lease
+ Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
+ SUBNET_ID);
+
+ // Use copy constructor to copy the lease.
+ Lease4 copied_lease(lease);
+
+ // Both leases should be now equal. When doing this check we assume that
+ // the equality operator works correctly.
+ EXPECT_TRUE(lease == copied_lease);
+ // Client IDs are equal, but they should be in two distinct pointers.
+ EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
+}
+
+// This test verfies that the assignment operator copies all Lease4 fields
+// correctly.
+TEST(Lease4, operatorAssign) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Create the lease
+ Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
+ SUBNET_ID);
+
+ // Use assignment operator to assign the lease.
+ Lease4 copied_lease = lease;
+
+ // Both leases should be now equal. When doing this check we assume that
+ // the equality operator works correctly.
+ EXPECT_TRUE(lease == copied_lease);
+ // Client IDs are equal, but they should be in two distinct pointers.
+ EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
+}
+
+// This test verifies that the matches() returns true if two leases differ
+// by values other than address, HW address, Client ID and ext_.
+TEST(Lease4, matches) {
+ // Create two leases which share the same address, HW address, client id
+ // and ext_ value.
+ const time_t current_time = time(NULL);
+ Lease4 lease1(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
+ sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
+ SUBNET_ID);
+ lease1.hostname_ = "lease1.example.com.";
+ lease1.fqdn_fwd_ = true;
+ lease1.fqdn_rev_ = true;
+ Lease4 lease2(IOAddress("192.0.2.3"), HWADDR, sizeof(HWADDR), CLIENTID,
+ sizeof(CLIENTID), VALID_LIFETIME + 10, current_time - 10,
+ 100, 200, SUBNET_ID);
+ lease2.hostname_ = "lease2.example.com.";
+ lease2.fqdn_fwd_ = false;
+ lease2.fqdn_rev_ = true;
+
+ // Leases should match.
+ EXPECT_TRUE(lease1.matches(lease2));
+ EXPECT_TRUE(lease2.matches(lease1));
+
+ // Change address, leases should not match anymore.
+ lease1.addr_ = IOAddress("192.0.2.4");
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.addr_ = lease2.addr_;
+
+ // Change HW address, leases should not match.
+ lease1.hwaddr_[1] += 1;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.hwaddr_ = lease2.hwaddr_;
+
+ // Chanage client id, leases should not match.
+ std::vector<uint8_t> client_id = lease1.client_id_->getClientId();
+ client_id[1] += 1;
+ lease1.client_id_.reset(new ClientId(client_id));
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.client_id_ = lease2.client_id_;
+
+ // Change ext_, leases should not match.
+ lease1.ext_ += 1;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.ext_ = lease2.ext_;
+}
+
+/// @brief Lease4 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease4, operatorEquals) {
+
+ // Random values for the tests
+ const uint32_t ADDRESS = 0x01020304;
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+ const time_t current_time = time(NULL);
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Check when the leases are equal.
+ Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0,
+ 0, SUBNET_ID);
+ Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
+ SUBNET_ID);
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Now vary individual fields in a lease and check that the leases compare
+ // not equal in every case.
+ lease1.addr_ = IOAddress(ADDRESS + 1);
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.ext_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.ext_ = lease2.ext_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.hwaddr_[0];
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hwaddr_ = lease2.hwaddr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t1_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t1_ = lease2.t1_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t2_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t2_ = lease2.t2_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fixed_ = !lease1.fixed_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fixed_ = lease2.fixed_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += std::string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.comments_ += std::string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.comments_ = lease2.comments_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
// Verify that the client id can be returned as a vector object and if client
// id is NULL the empty vector is returned.
-TEST(Lease4Test, getClientIdVector) {
+TEST(Lease4, getClientIdVector) {
// Create a lease.
Lease4 lease;
// By default, the lease should have client id set to NULL. If it doesn't,
@@ -47,9 +366,356 @@ TEST(Lease4Test, getClientIdVector) {
EXPECT_TRUE(returned_vec == client_id_vec);
}
+// Verify the behavior of the function which checks FQDN data for equality.
+TEST(Lease4, hasIdenticalFqdn) {
+ Lease4 lease = createLease4("myhost.example.com.", true, true);
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("other.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ false, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ true, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ false, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("other.example.com.",
+ false, false)));
+}
+
+/// @brief Creates an instance of the lease with certain FQDN data.
+///
+/// @param hostname Hostname.
+/// @param fqdn_fwd Forward FQDN update setting for a created lease.
+/// @param fqdn_rev Reverse FQDN update setting for a created lease.
+///
+/// @return Instance of the created lease.
+Lease6 createLease6(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ Lease6 lease;
+ lease.hostname_ = hostname;
+ lease.fqdn_fwd_ = fqdn_fwd;
+ lease.fqdn_rev_ = fqdn_rev;
+ return (lease);
+}
+
+// Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease6 structure can be instantiated correctly
+TEST(Lease6, Lease6ConstructorDefault) {
+
+ // check a variety of addresses with different bits set.
+ const char* ADDRESS[] = {
+ "::", "::1", "2001:db8:1::456",
+ "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "8000::", "8000::1",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ };
+
+ // Other values
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+ uint32_t iaid = 7; // Just a number
+ SubnetID subnet_id = 8; // Just another number
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+ IOAddress addr(ADDRESS[i]);
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200, 50, 80,
+ subnet_id));
+
+ EXPECT_TRUE(lease->addr_ == addr);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_TRUE(lease->iaid_ == iaid);
+ EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+ EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
+ EXPECT_TRUE(lease->preferred_lft_ == 100);
+ EXPECT_TRUE(lease->valid_lft_ == 200);
+ EXPECT_TRUE(lease->t1_ == 50);
+ EXPECT_TRUE(lease->t2_ == 80);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_TRUE(lease->hostname_.empty());
+
+ }
+
+ // Lease6 must be instantiated with a DUID, not with NULL pointer
+ IOAddress addr(ADDRESS[0]);
+ Lease6Ptr lease2;
+ EXPECT_THROW(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200, 50, 80,
+ subnet_id)), InvalidOperation);
+}
+
+// This test verifies that the Lease6 constructor which accepts FQDN data,
+// sets the data correctly for the lease.
+TEST(Lease6, Lease6ConstructorWithFQDN) {
+
+ // check a variety of addresses with different bits set.
+ const char* ADDRESS[] = {
+ "::", "::1", "2001:db8:1::456",
+ "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "8000::", "8000::1",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ };
+
+ // Other values
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+ uint32_t iaid = 7; // Just a number
+ SubnetID subnet_id = 8; // Just another number
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+ IOAddress addr(ADDRESS[i]);
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200, 50, 80, subnet_id,
+ true, true, "host.example.com."));
+
+ EXPECT_TRUE(lease->addr_ == addr);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_TRUE(lease->iaid_ == iaid);
+ EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+ EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
+ EXPECT_TRUE(lease->preferred_lft_ == 100);
+ EXPECT_TRUE(lease->valid_lft_ == 200);
+ EXPECT_TRUE(lease->t1_ == 50);
+ EXPECT_TRUE(lease->t2_ == 80);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("host.example.com.", lease->hostname_);
+ }
+
+ // Lease6 must be instantiated with a DUID, not with NULL pointer
+ IOAddress addr(ADDRESS[0]);
+ Lease6Ptr lease2;
+ EXPECT_THROW(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200, 50, 80,
+ subnet_id)), InvalidOperation);
+}
+
+// This test verifies that the matches() function returns true if two leases
+// differ by values other than address, type, prefix length, IAID and DUID.
+TEST(Lease6, matches) {
+
+ // Create two matching leases.
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease1(Lease6::TYPE_NA, IOAddress("2001:db8:1::1"), duid,
+ IAID, 100, 200, 50, 80,
+ SUBNET_ID);
+ lease1.hostname_ = "lease1.example.com.";
+ lease1.fqdn_fwd_ = true;
+ lease1.fqdn_rev_ = true;
+ Lease6 lease2(Lease6::TYPE_NA, IOAddress("2001:db8:1::1"), duid,
+ IAID, 200, 300, 90, 70,
+ SUBNET_ID);
+ lease2.hostname_ = "lease1.example.com.";
+ lease2.fqdn_fwd_ = false;
+ lease2.fqdn_rev_ = true;
+
+ EXPECT_TRUE(lease1.matches(lease2));
+
+ // Modify each value used to match both leases, and make sure that
+ // leases don't match.
+
+ // Modify address.
+ lease1.addr_ = IOAddress("2001:db8:1::2");
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.addr_ = lease2.addr_;
+
+ // Modify lease type.
+ lease1.type_ = Lease6::TYPE_TA;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.type_ = lease2.type_;
+
+ // Modify prefix length.
+ lease1.prefixlen_ += 1;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.prefixlen_ = lease2.prefixlen_;
+
+ // Modify IAID.
+ lease1.iaid_ += 1;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.iaid_ = lease2.iaid_;
+
+ // Modify DUID.
+ llt[1] += 1;
+ duid.reset(new DUID(llt, sizeof(llt)));
+ lease1.duid_ = duid;
+ EXPECT_FALSE(lease1.matches(lease2));
+ lease1.duid_ = lease2.duid_;
+}
+
+/// @brief Lease6 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease6, OperatorEquals) {
+
+ // check a variety of addresses with different bits set.
+ const IOAddress addr("2001:db8:1::456");
+ uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ uint32_t iaid = 7; // just a number
+ SubnetID subnet_id = 8; // just another number
+
+ // Check for equality.
+ Lease6 lease1(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+ Lease6 lease2(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+
+ // cltt_ constructs with time(NULL), make sure they are always equal
+ lease1.cltt_ = lease2.cltt_;
+
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Go through and alter all the fields one by one
+
+ lease1.addr_ = IOAddress("::1");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.type_ = Lease::TYPE_PD;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.type_ = lease2.type_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.prefixlen_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.prefixlen_ = lease2.prefixlen_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.iaid_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.iaid_ = lease2.iaid_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.preferred_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.preferred_lft_ = lease2.preferred_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t1_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t1_ = lease2.t1_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t2_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t2_ = lease2.t2_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fixed_ = !lease1.fixed_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fixed_ = lease2.fixed_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += std::string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.comments_ += std::string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.comments_ = lease2.comments_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+// Checks if lease expiration is calculated properly
+TEST(Lease6, Lease6Expired) {
+ const IOAddress addr("2001:db8:1::456");
+ const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ const uint32_t iaid = 7; // Just a number
+ const SubnetID subnet_id = 8; // Just another number
+ Lease6 lease(Lease::TYPE_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+
+ // Case 1: a second before expiration
+ lease.cltt_ = time(NULL) - 100;
+ lease.valid_lft_ = 101;
+ EXPECT_FALSE(lease.expired());
+
+ // Case 2: the lease will expire after this second is concluded
+ lease.cltt_ = time(NULL) - 101;
+ EXPECT_FALSE(lease.expired());
+
+ // Case 3: the lease is expired
+ lease.cltt_ = time(NULL) - 102;
+ EXPECT_TRUE(lease.expired());
+}
+
// Verify that the DUID can be returned as a vector object and if DUID is NULL
// the empty vector is returned.
-TEST(Lease6Test, getDuidVector) {
+TEST(Lease6, getDuidVector) {
// Create a lease.
Lease6 lease;
// By default, the lease should have client id set to NULL. If it doesn't,
@@ -67,5 +733,21 @@ TEST(Lease6Test, getDuidVector) {
EXPECT_TRUE(returned_vec == duid_vec);
}
+// Verify the behavior of the function which checks FQDN data for equality.
+TEST(Lease6, hasIdenticalFqdn) {
+ Lease6 lease = createLease6("myhost.example.com.", true, true);
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("other.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ false, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ true, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ false, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("other.example.com.",
+ false, false)));
+}
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index 85015f623d..657075001f 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -95,7 +95,7 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
IOAddress("2001:db8:1::456"));
ASSERT_TRUE(x);
- EXPECT_EQ(x->addr_.toText(), addr.toText());
+ EXPECT_EQ(x->addr_, addr);
EXPECT_TRUE(*x->duid_ == *duid);
EXPECT_EQ(x->iaid_, iaid);
EXPECT_EQ(x->subnet_id_, subnet_id);
@@ -114,7 +114,7 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
ASSERT_TRUE(y);
EXPECT_TRUE(*y->duid_ == *duid);
EXPECT_EQ(y->iaid_, iaid);
- EXPECT_EQ(y->addr_.toText(), addr.toText());
+ EXPECT_EQ(y->addr_, addr);
// Test getLease6(duid, iaid, subnet_id) - wrong iaid
uint32_t invalid_iaid = 9; // no such iaid
@@ -144,7 +144,7 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
EXPECT_EQ(Lease6Ptr(), x);
}
-// @todo Write more memfile tests
+/// @todo Write more memfile tests
// Simple test about lease4 retrieval through client id method
TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index d5e00ab1ea..578ef3c089 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -549,7 +549,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
}
// Get the leases matching the hardware address of lease 1
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
Lease4Collection returned = lmptr_->getLease4(tmp);
@@ -568,14 +568,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
EXPECT_EQ(straddress4_[5], addresses[2]);
// Repeat test with just one expected match
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[2], *returned.begin());
// Check that an empty vector is valid
EXPECT_TRUE(leases[7]->hwaddr_.empty());
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
@@ -599,7 +599,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
Lease4Collection returned =
lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
@@ -610,7 +610,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
// Database should not let us add one that is too big
// (The 42 is a random value put in each byte of the address.)
- // @todo: 2589 will make this test impossible
+ /// @todo: 2589 will make this test impossible
leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
}
@@ -628,7 +628,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Get the leases matching the hardware address of lease 1 and
// subnet ID of lease 1. Result should be a single lease - lease 1.
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
HTYPE_ETHER), leases[1]->subnet_id_);
@@ -637,7 +637,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Try for a match to the hardware address of lease 1 and the wrong
// subnet ID.
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
@@ -645,14 +645,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Try for a match to the subnet ID of lease 1 (and lease 4) but
// the wrong hardware address.
vector<uint8_t> invalid_hwaddr(15, 0x77);
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_);
EXPECT_FALSE(returned);
// Try for a match to an unknown hardware address and an unknown
// subnet ID.
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
@@ -665,7 +665,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
leases[1]->addr_ = leases[2]->addr_;
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
HTYPE_ETHER),
leases[1]->subnet_id_),
@@ -687,7 +687,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
HTYPE_ETHER),
leases[1]->subnet_id_);
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
index e418c6208d..44d60ea4ff 100644
--- a/src/lib/dhcpsrv/tests/test_utils.cc
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -48,7 +48,7 @@ detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
// odd things happen when they are different: the EXPECT_EQ macro appears to
// call the operator uint32_t() function, which causes an exception to be
// thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_EQ(first->addr_, second->addr_);
EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
if (first->client_id_ && second->client_id_) {
EXPECT_TRUE(*first->client_id_ == *second->client_id_);
@@ -83,7 +83,7 @@ detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
// odd things happen when they are different: the EXPECT_EQ macro appears to
// call the operator uint32_t() function, which causes an exception to be
// thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_EQ(first->addr_, second->addr_);
EXPECT_EQ(first->prefixlen_, second->prefixlen_);
EXPECT_EQ(first->iaid_, second->iaid_);
ASSERT_TRUE(first->duid_);
diff --git a/src/lib/dns/.gitignore b/src/lib/dns/.gitignore
index cdf707c3a1..9606daca0b 100644
--- a/src/lib/dns/.gitignore
+++ b/src/lib/dns/.gitignore
@@ -4,3 +4,4 @@
/rrclass.h
/rrparamregistry.cc
/rrtype.h
+/s-rdatacode
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index bda4e85333..ede699a2b7 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -158,7 +158,7 @@ rrclass.h: rrclass-placeholder.h
rrtype.h: rrtype-placeholder.h
rrparamregistry.cc: rrparamregistry-placeholder.cc
-s-rdatacode: Makefile
+s-rdatacode: Makefile $(EXTRA_DIST)
$(PYTHON) ./gen-rdatacode.py
touch $@
diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc
index f5f6a95a1b..91af63372d 100644
--- a/src/lib/dns/labelsequence.cc
+++ b/src/lib/dns/labelsequence.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <dns/labelsequence.h>
#include <dns/name_internal.h>
#include <exceptions/exceptions.h>
@@ -24,24 +26,34 @@ namespace isc {
namespace dns {
LabelSequence::LabelSequence(const void* buf) {
+#ifdef ENABLE_DEBUG
+ // In non-debug mode, derefencing the NULL pointer further below
+ // will lead to a crash, so disabling this check is not
+ // unsafe. Except for a programming mistake, this case should not
+ // happen.
if (buf == NULL) {
isc_throw(BadValue,
"Null pointer passed to LabelSequence constructor");
}
+#endif
const uint8_t* bp = reinterpret_cast<const uint8_t*>(buf);
-
first_label_ = 0;
const uint8_t offsets_len = *bp++;
+
+#ifdef ENABLE_DEBUG
if (offsets_len == 0 || offsets_len > Name::MAX_LABELS) {
isc_throw(BadValue,
"Bad offsets len in serialized LabelSequence data: "
<< static_cast<unsigned int>(offsets_len));
}
+#endif
+
last_label_ = offsets_len - 1;
offsets_ = bp;
data_ = bp + offsets_len;
+#ifdef ENABLE_DEBUG
// Check the integrity on the offsets and the name data
const uint8_t* dp = data_;
for (size_t cur_offset = 0; cur_offset < offsets_len; ++cur_offset) {
@@ -52,6 +64,7 @@ LabelSequence::LabelSequence(const void* buf) {
}
dp += (1 + *dp);
}
+#endif
}
LabelSequence::LabelSequence(const LabelSequence& src,
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index 2bc337ad6f..33cd07b2dc 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -604,13 +604,8 @@ Message::addQuestion(const Question& question) {
}
void
-Message::toWire(AbstractMessageRenderer& renderer) {
- impl_->toWire(renderer, NULL);
-}
-
-void
-Message::toWire(AbstractMessageRenderer& renderer, TSIGContext& tsig_ctx) {
- impl_->toWire(renderer, &tsig_ctx);
+Message::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) {
+ impl_->toWire(renderer, tsig_ctx);
}
void
@@ -620,6 +615,10 @@ Message::parseHeader(InputBuffer& buffer) {
"Message parse attempted in non parse mode");
}
+ if (impl_->header_parsed_) {
+ return;
+ }
+
if ((buffer.getLength() - buffer.getPosition()) < HEADERLEN) {
isc_throw(MessageTooShort, "Malformed DNS message (short length): "
<< buffer.getLength() - buffer.getPosition());
@@ -645,9 +644,11 @@ Message::fromWire(InputBuffer& buffer, ParseOptions options) {
"Message parse attempted in non parse mode");
}
- if (!impl_->header_parsed_) {
- parseHeader(buffer);
- }
+ // Clear any old parsed data
+ clear(Message::PARSE);
+
+ buffer.setPosition(0);
+ parseHeader(buffer);
impl_->counts_[SECTION_QUESTION] = impl_->parseQuestion(buffer);
impl_->counts_[SECTION_ANSWER] =
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index aaa0d76982..828346fef1 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -550,29 +550,18 @@ public:
std::string toText() const;
/// \brief Render the message in wire formant into a message renderer
- /// object.
+ /// object with (or without) TSIG.
///
/// This \c Message must be in the \c RENDER mode and both \c Opcode and
/// \c Rcode must have been set beforehand; otherwise, an exception of
/// class \c InvalidMessageOperation will be thrown.
///
- /// \note The renderer's internal buffers and data are automatically
- /// cleared, keeping the length limit and the compression mode intact.
- /// In case truncation is triggered, the renderer is cleared completely.
- ///
- /// \param renderer DNS message rendering context that encapsulates the
- /// output buffer and name compression information.
- void toWire(AbstractMessageRenderer& renderer);
-
- /// \brief Render the message in wire formant into a message renderer
- /// object with TSIG.
- ///
- /// This method is similar to the other version of \c toWire(), but
- /// it will also add a TSIG RR with (in many cases) the TSIG MAC for
- /// the message along with the given TSIG context (\c tsig_ctx).
- /// The TSIG RR will be placed at the end of \c renderer.
- /// \c tsig_ctx will be updated based on the fact it was used for signing
- /// and with the latest MAC.
+ /// If a non-NULL \c tsig_ctx is passed, it will also add a TSIG RR
+ /// with (in many cases) the TSIG MAC for the message along with the
+ /// given TSIG context (\c tsig_ctx). The TSIG RR will be placed at
+ /// the end of \c renderer. The \c TSIGContext at \c tsig_ctx will
+ /// be updated based on the fact it was used for signing and with
+ /// the latest MAC.
///
/// \exception InvalidMessageOperation The message is not in the Render
/// mode, or either Rcode or Opcode is not set.
@@ -589,10 +578,12 @@ public:
/// cleared, keeping the length limit and the compression mode intact.
/// In case truncation is triggered, the renderer is cleared completely.
///
- /// \param renderer See the other version
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
/// \param tsig_ctx A TSIG context that is to be used for signing the
/// message
- void toWire(AbstractMessageRenderer& renderer, TSIGContext& tsig_ctx);
+ void toWire(AbstractMessageRenderer& renderer,
+ TSIGContext* tsig_ctx = NULL);
/// Parse options.
///
@@ -607,6 +598,10 @@ public:
};
/// \brief Parse the header section of the \c Message.
+ ///
+ /// NOTE: If the header has already been parsed by a previous call
+ /// to this method, this method simply returns (i.e., it does not
+ /// read from the \c buffer).
void parseHeader(isc::util::InputBuffer& buffer);
/// \brief (Re)build a \c Message object from wire-format data.
@@ -642,7 +637,8 @@ public:
/// \exception std::bad_alloc Memory allocation failure
/// \exception Others \c Name, \c Rdata, and \c EDNS classes can also throw
///
- /// \param buffer A input buffer object that stores the wire data
+ /// \param buffer A input buffer object that stores the wire
+ /// data. This method reads from position 0 in the passed buffer.
/// \param options Parse options
void fromWire(isc::util::InputBuffer& buffer, ParseOptions options
= PARSE_DEFAULT);
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
index f1ca1a3c2c..42b5117cb4 100644
--- a/src/lib/dns/nsec3hash.h
+++ b/src/lib/dns/nsec3hash.h
@@ -122,7 +122,7 @@ public:
/// (SHA-1) is supported
/// \param iterations the number of iterations
/// \param salt_data the salt data as a byte array
- /// \param salt_data_length the length of the salt data
+ /// \param salt_length the length of the salt data
static NSEC3Hash* create(uint8_t algorithm, uint16_t iterations,
const uint8_t* salt_data, size_t salt_length);
@@ -233,7 +233,7 @@ public:
/// (SHA-1) is supported
/// \param iterations the number of iterations
/// \param salt_data the salt data as a byte array
- /// \param salt_data_length the length of the salt data
+ /// \param salt_length the length of the salt data
virtual NSEC3Hash* create(uint8_t algorithm, uint16_t iterations,
const uint8_t* salt_data, size_t salt_length)
const = 0;
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 4dfee249b6..d86754b8e8 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -696,10 +696,10 @@ Message_toWire(s_Message* self, PyObject* args) {
PyObject* mr;
PyObject* tsig_ctx = NULL;
- if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
- &tsigcontext_type, &tsig_ctx)) {
+ if (PyArg_ParseTuple(args, "O!|O", &messagerenderer_type, &mr,
+ &tsig_ctx)) {
try {
- if (tsig_ctx == NULL) {
+ if ((tsig_ctx == NULL) || (tsig_ctx == Py_None)) {
self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr));
} else {
self->cppobj->toWire(PyMessageRenderer_ToMessageRenderer(mr),
@@ -726,7 +726,7 @@ Message_toWire(s_Message* self, PyObject* args) {
}
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
- "toWire argument must be a MessageRenderer");
+ "Bad to_wire() arguments were passed");
return (NULL);
}
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index 20f67c8d7e..682a16801b 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -104,9 +104,9 @@ Rdata_init(PyObject* self_p, PyObject* args, PyObject*) {
const char* s;
const char* data;
Py_ssize_t len;
- s_Rdata* self(static_cast<s_Rdata*>(self_p));
-
try {
+ s_Rdata* self = static_cast<s_Rdata*>(self_p);
+
// Create from string
if (PyArg_ParseTuple(args, "O!O!s", &rrtype_type, &rrtype,
&rrclass_type, &rrclass,
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index de5925a431..4d864d1d8a 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -53,6 +53,7 @@ int RRset_init(s_RRset* self, PyObject* args);
void RRset_destroy(s_RRset* self);
PyObject* RRset_getRdataCount(PyObject* self, PyObject* args);
+PyObject* RRset_getLength(PyObject* self, PyObject* args);
PyObject* RRset_getName(PyObject* self, PyObject* args);
PyObject* RRset_getClass(PyObject* self, PyObject* args);
PyObject* RRset_getType(PyObject* self, PyObject* args);
@@ -70,6 +71,8 @@ PyObject* RRset_removeRRsig(PyObject* self, PyObject* args);
PyMethodDef RRset_methods[] = {
{ "get_rdata_count", RRset_getRdataCount, METH_NOARGS,
"Returns the number of rdata fields." },
+ { "get_length", RRset_getLength, METH_NOARGS,
+ "Returns the wire format length of the RRset." },
{ "get_name", RRset_getName, METH_NOARGS,
"Returns the name of the RRset, as a Name object." },
{ "get_class", RRset_getClass, METH_NOARGS,
@@ -136,6 +139,18 @@ RRset_getRdataCount(PyObject* self, PyObject*) {
}
PyObject*
+RRset_getLength(PyObject* self, PyObject*) {
+ try {
+ return (Py_BuildValue("H", static_cast<const s_RRset*>(self)->cppobj->
+ getLength()));
+ } catch (const EmptyRRset& ers) {
+ PyErr_Clear();
+ PyErr_SetString(po_EmptyRRset, ers.what());
+ return (NULL);
+ }
+}
+
+PyObject*
RRset_getName(PyObject* self, PyObject*) {
try {
return (createNameObject(static_cast<const s_RRset*>(self)->cppobj->
@@ -236,9 +251,9 @@ PyObject*
RRset_toWire(PyObject* self_p, PyObject* args) {
PyObject* bytes;
PyObject* mr;
- const s_RRset* self(static_cast<const s_RRset*>(self_p));
try {
+ const s_RRset* self = static_cast<const s_RRset*>(self_p);
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 996ef8970c..6f017dfec3 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -371,6 +371,13 @@ class MessageTest(unittest.TestCase):
self.__common_tsigmessage_setup()
self.__common_tsig_checks("message_toWire2.wire")
+ def test_to_wire_with_tsig_none(self):
+ message_render = create_message()
+ renderer = MessageRenderer()
+ message_render.to_wire(renderer, None)
+ self.assertEqual(b'\x105\x85\x00\x00\x01\x00\x02\x00\x00\x00\x00\x04test\x07example\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x02',
+ renderer.get_data())
+
def test_to_wire_with_edns_tsig(self):
fix_current_time(0x4db60d1f)
self.r.set_qid(0x6cd)
diff --git a/src/lib/dns/python/tests/rrset_python_test.py b/src/lib/dns/python/tests/rrset_python_test.py
index 9592b42021..d848d278b6 100644
--- a/src/lib/dns/python/tests/rrset_python_test.py
+++ b/src/lib/dns/python/tests/rrset_python_test.py
@@ -45,6 +45,24 @@ class TestModuleSpec(unittest.TestCase):
self.assertEqual(i, self.rrset_a_empty.get_rdata_count())
self.rrset_a_empty.add_rdata(Rdata(RRType("A"), RRClass("IN"), "192.0.2.1"))
+ def test_get_length(self):
+ # Empty RRset should throw
+ self.assertRaises(EmptyRRset, self.rrset_a_empty.get_length);
+
+ # Unless it is type ANY or NONE:
+ # test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ # TYPE field = 2 octets
+ # CLASS field = 2 octets
+ # TTL field = 4 octets
+ # RDLENGTH field = 2 octets
+ # Total = 18 + 2 + 2 + 4 + 2 = 28 octets
+ self.assertEqual(28, self.rrset_any_a_empty.get_length())
+
+ # Single A RR:
+ # 28 octets (above) + 4 octets (A RDATA) = 32 octets
+ # With 2 A RRs:
+ self.assertEqual(32 + 32, self.rrset_a.get_length())
+
def test_get_name(self):
self.assertEqual(self.test_name, self.rrset_a.get_name())
self.assertEqual(self.test_domain, self.rrset_ns.get_name())
diff --git a/src/lib/dns/python/tsig_python.cc b/src/lib/dns/python/tsig_python.cc
index abb77334f6..c5b5421dcc 100644
--- a/src/lib/dns/python/tsig_python.cc
+++ b/src/lib/dns/python/tsig_python.cc
@@ -334,14 +334,21 @@ PyTSIGContext_Check(PyObject* obj) {
return (PyObject_TypeCheck(obj, &tsigcontext_type));
}
-TSIGContext&
+TSIGContext*
PyTSIGContext_ToTSIGContext(PyObject* tsigcontext_obj) {
if (tsigcontext_obj == NULL) {
isc_throw(PyCPPWrapperException,
"obj argument NULL in TSIGContext PyObject conversion");
}
+
+ if (!PyTSIGContext_Check(tsigcontext_obj)) {
+ isc_throw(TSIGContextError,
+ "obj argument is of wrong type in TSIGContext "
+ "PyObject conversion");
+ }
+
s_TSIGContext* tsigcontext = static_cast<s_TSIGContext*>(tsigcontext_obj);
- return (*tsigcontext->cppobj);
+ return (tsigcontext->cppobj);
}
} // namespace python
diff --git a/src/lib/dns/python/tsig_python.h b/src/lib/dns/python/tsig_python.h
index 0bd57d70c0..bbef56be10 100644
--- a/src/lib/dns/python/tsig_python.h
+++ b/src/lib/dns/python/tsig_python.h
@@ -36,8 +36,8 @@ extern PyObject* po_TSIGContextError;
/// \return true if the object is of type TSIGContext, false otherwise
bool PyTSIGContext_Check(PyObject* obj);
-/// \brief Returns a reference to the TSIGContext object contained within the given
-/// Python object.
+/// \brief Returns a pointer to the TSIGContext object contained within
+/// the given Python object.
///
/// \note The given object MUST be of type TSIGContext; this can be checked with
/// either the right call to ParseTuple("O!"), or with PyTSIGContext_Check()
@@ -46,7 +46,7 @@ bool PyTSIGContext_Check(PyObject* obj);
/// may be destroyed, the caller must copy it itself.
///
/// \param tsigcontext_obj The tsigcontext object to convert
-TSIGContext& PyTSIGContext_ToTSIGContext(PyObject* tsigcontext_obj);
+TSIGContext* PyTSIGContext_ToTSIGContext(PyObject* tsigcontext_obj);
} // namespace python
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index f42c349f2d..ff95fee6b0 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -46,6 +46,15 @@ namespace isc {
namespace dns {
namespace rdata {
+uint16_t
+Rdata::getLength() const {
+ OutputBuffer obuffer(0);
+
+ toWire(obuffer);
+
+ return (obuffer.getLength());
+}
+
// XXX: we need to specify std:: for string to help doxygen match the
// function signature with that given in the header file.
RdataPtr
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 3fe0c74dd2..79ca641c80 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -221,6 +221,21 @@ public:
/// \return > 0 if \c this would be sorted after \c other.
virtual int compare(const Rdata& other) const = 0;
//@}
+
+ /// \brief Get the wire format length of an Rdata.
+ ///
+ /// IMPLEMENTATION NOTE: Currently this base class implementation is
+ /// non-optimal as it renders the wire data to a buffer and returns
+ /// the buffer's length. What would perform better is to add
+ /// implementations of \c getLength() method to every RDATA
+ /// type. This is why this method is virtual. Once all Rdata types
+ /// have \c getLength() implementations, this base class
+ /// implementation must be removed and the method should become a
+ /// pure interface.
+ ///
+ /// \return The length of the wire format representation of the
+ /// RDATA.
+ virtual uint16_t getLength() const;
};
namespace generic {
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index 2865ed18c8..4c1e83274a 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -14,8 +14,6 @@
#include <config.h>
-#include <string>
-
#include <boost/lexical_cast.hpp>
#include <exceptions/exceptions.h>
@@ -38,7 +36,7 @@ using namespace isc::util::encode;
struct SSHFPImpl {
// straightforward representation of SSHFP RDATA fields
SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
- vector<uint8_t>& fingerprint) :
+ const vector<uint8_t>& fingerprint) :
algorithm_(algorithm),
fingerprint_type_(fingerprint_type),
fingerprint_(fingerprint)
@@ -82,7 +80,11 @@ SSHFP::constructFromLexer(MasterLexer& lexer) {
// If fingerprint is missing, it's OK. See the API documentation of the
// constructor.
if (fingerprint_str.size() > 0) {
- decodeHex(fingerprint_str, fingerprint);
+ try {
+ decodeHex(fingerprint_str, fingerprint);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
+ }
}
return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
@@ -102,8 +104,9 @@ SSHFP::constructFromLexer(MasterLexer& lexer) {
/// valid hex encoding of the fingerprint. For compatibility with BIND 9,
/// whitespace is allowed in the hex text (RFC4255 is silent on the matter).
///
-/// \throw InvalidRdataText if any fields are missing, out of their valid
-/// ranges, or incorrect.
+/// \throw InvalidRdataText if any fields are missing, are out of their
+/// valid ranges or are incorrect, or if the fingerprint is not a valid
+/// hex string.
///
/// \param sshfp_str A string containing the RDATA to be created
SSHFP::SSHFP(const string& sshfp_str) :
@@ -128,9 +131,6 @@ SSHFP::SSHFP(const string& sshfp_str) :
} catch (const MasterLexer::LexerError& ex) {
isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
sshfp_str << "': " << ex.what());
- } catch (const isc::BadValue& e) {
- isc_throw(InvalidRdataText,
- "Bad SSHFP fingerprint: " << e.what());
}
impl_ = impl_ptr.release();
@@ -142,9 +142,8 @@ SSHFP::SSHFP(const string& sshfp_str) :
/// of an SSHFP RDATA.
///
/// \throw MasterLexer::LexerError General parsing error such as missing field.
-/// \throw InvalidRdataText Fields are out of their valid range, or are
-/// incorrect.
-/// \throw BadValue Fingerprint is not a valid hex string.
+/// \throw InvalidRdataText Fields are out of their valid range or are
+/// incorrect, or if the fingerprint is not a valid hex string.
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
@@ -293,8 +292,13 @@ SSHFP::getFingerprintType() const {
return (impl_->fingerprint_type_);
}
+const std::vector<uint8_t>&
+SSHFP::getFingerprint() const {
+ return (impl_->fingerprint_);
+}
+
size_t
-SSHFP::getFingerprintLen() const {
+SSHFP::getFingerprintLength() const {
return (impl_->fingerprint_.size());
}
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
index 28ce0f3d22..85ae8067c5 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.h
+++ b/src/lib/dns/rdata/generic/sshfp_44.h
@@ -17,6 +17,7 @@
#include <stdint.h>
#include <string>
+#include <vector>
#include <dns/name.h>
#include <dns/rdata.h>
@@ -45,7 +46,8 @@ public:
///
uint8_t getAlgorithmNumber() const;
uint8_t getFingerprintType() const;
- size_t getFingerprintLen() const;
+ const std::vector<uint8_t>& getFingerprint() const;
+ size_t getFingerprintLength() const;
private:
SSHFPImpl* constructFromLexer(MasterLexer& lexer);
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
index 0d34d1921b..b3da32c9de 100644
--- a/src/lib/dns/rrset.cc
+++ b/src/lib/dns/rrset.cc
@@ -17,6 +17,7 @@
#include <vector>
#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
#include <util/buffer.h>
#include <dns/messagerenderer.h>
@@ -71,7 +72,10 @@ AbstractRRset::toText() const {
return (s);
}
-namespace {
+namespace { // unnamed namespace
+
+// FIXME: This method's code should somehow be unified with
+// BasicRRsetImpl::toWire() below to avoid duplication.
template <typename T>
inline unsigned int
rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
@@ -124,7 +128,8 @@ rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
return (n);
}
-}
+
+} // end of unnamed namespace
unsigned int
AbstractRRset::toWire(OutputBuffer& buffer) const {
@@ -164,6 +169,9 @@ public:
BasicRRsetImpl(const Name& name, const RRClass& rrclass,
const RRType& rrtype, const RRTTL& ttl) :
name_(name), rrclass_(rrclass), rrtype_(rrtype), ttl_(ttl) {}
+
+ unsigned int toWire(AbstractMessageRenderer& renderer, size_t limit) const;
+
Name name_;
RRClass rrclass_;
RRType rrtype_;
@@ -174,6 +182,58 @@ public:
vector<ConstRdataPtr> rdatalist_;
};
+// FIXME: This method's code should somehow be unified with
+// rrsetToWire() above to avoid duplication.
+unsigned int
+BasicRRsetImpl::toWire(AbstractMessageRenderer& renderer, size_t limit) const {
+ if (rdatalist_.empty()) {
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (rrclass_ != RRClass::ANY() &&
+ rrclass_ != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset");
+ }
+
+ // For an empty RRset, write the name, type, class and TTL once,
+ // followed by empty rdata.
+ name_.toWire(renderer);
+ rrtype_.toWire(renderer);
+ rrclass_.toWire(renderer);
+ ttl_.toWire(renderer);
+ renderer.writeUint16(0);
+ // Still counts as 1 'rr'; it does show up in the message
+ return (1);
+ }
+
+ unsigned int n = 0;
+
+ // sort the set of Rdata based on rrset-order and sortlist, and possible
+ // other options. Details to be considered.
+ BOOST_FOREACH(const ConstRdataPtr& rdata, rdatalist_) {
+ const size_t pos0 = renderer.getLength();
+ assert(pos0 < 65536);
+
+ name_.toWire(renderer);
+ rrtype_.toWire(renderer);
+ rrclass_.toWire(renderer);
+ ttl_.toWire(renderer);
+
+ const size_t pos = renderer.getLength();
+ renderer.skip(sizeof(uint16_t)); // leave the space for RDLENGTH
+ rdata->toWire(renderer);
+ renderer.writeUint16At(renderer.getLength() - pos - sizeof(uint16_t),
+ pos);
+
+ if (limit > 0 && renderer.getLength() > limit) {
+ // truncation is needed
+ renderer.trim(renderer.getLength() - pos0);
+ return (n);
+ }
+ ++n;
+ }
+
+ return (n);
+}
+
BasicRRset::BasicRRset(const Name& name, const RRClass& rrclass,
const RRType& rrtype, const RRTTL& ttl)
{
@@ -194,6 +254,11 @@ BasicRRset::addRdata(const Rdata& rdata) {
AbstractRRset::addRdata(rdata);
}
+void
+BasicRRset::addRdata(const std::string& rdata_str) {
+ addRdata(createRdata(getType(), getClass(), rdata_str));
+}
+
unsigned int
BasicRRset::getRdataCount() const {
return (impl_->rdatalist_.size());
@@ -229,6 +294,50 @@ BasicRRset::toText() const {
return (AbstractRRset::toText());
}
+uint16_t
+BasicRRset::getLength() const {
+ uint16_t length = 0;
+ RdataIteratorPtr it = getRdataIterator();
+
+ if (it->isLast()) {
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (getClass() != RRClass::ANY() &&
+ getClass() != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "getLength() is attempted for an empty RRset");
+ }
+
+ // For an empty RRset, write the name, type, class and TTL once,
+ // followed by empty rdata.
+ length += getName().getLength();
+ length += 2; // TYPE field
+ length += 2; // CLASS field
+ length += 4; // TTL field
+ length += 2; // RDLENGTH field (=0 in wire format)
+
+ return (length);
+ }
+
+ do {
+ // This is a size_t as some of the following additions may
+ // overflow due to a programming mistake somewhere.
+ size_t rrlen = 0;
+
+ rrlen += getName().getLength();
+ rrlen += 2; // TYPE field
+ rrlen += 2; // CLASS field
+ rrlen += 4; // TTL field
+ rrlen += 2; // RDLENGTH field
+ rrlen += it->getCurrent().getLength();
+
+ assert(length + rrlen < 65536);
+ length += rrlen;
+
+ it->next();
+ } while (!it->isLast());
+
+ return (length);
+}
+
unsigned int
BasicRRset::toWire(OutputBuffer& buffer) const {
return (AbstractRRset::toWire(buffer));
@@ -236,7 +345,12 @@ BasicRRset::toWire(OutputBuffer& buffer) const {
unsigned int
BasicRRset::toWire(AbstractMessageRenderer& renderer) const {
- return (AbstractRRset::toWire(renderer));
+ const unsigned int rrs_written = impl_->toWire(renderer,
+ renderer.getLengthLimit());
+ if (impl_->rdatalist_.size() > rrs_written) {
+ renderer.setTruncated();
+ }
+ return (rrs_written);
}
RRset::RRset(const Name& name, const RRClass& rrclass,
@@ -257,17 +371,30 @@ RRset::getRRsigDataCount() const {
}
}
+uint16_t
+RRset::getLength() const {
+ uint16_t length = BasicRRset::getLength();
+
+ if (rrsig_) {
+ const uint16_t rrsigs_length = rrsig_->getLength();
+ // the uint16_ts are promoted to ints during addition below, so
+ // it won't overflow a 16-bit register.
+ assert(length + rrsigs_length < 65536);
+ length += rrsigs_length;
+ }
+
+ return (length);
+}
+
unsigned int
RRset::toWire(OutputBuffer& buffer) const {
- unsigned int rrs_written;
-
- rrs_written = rrsetToWire<OutputBuffer>(*this, buffer, 0);
+ unsigned int rrs_written = BasicRRset::toWire(buffer);
if (getRdataCount() > rrs_written) {
return (rrs_written);
}
if (rrsig_) {
- rrs_written += rrsetToWire<OutputBuffer>(*(rrsig_.get()), buffer, 0);
+ rrs_written += rrsig_->toWire(buffer);
}
return (rrs_written);
@@ -275,24 +402,17 @@ RRset::toWire(OutputBuffer& buffer) const {
unsigned int
RRset::toWire(AbstractMessageRenderer& renderer) const {
- unsigned int rrs_written;
-
- rrs_written =
- rrsetToWire<AbstractMessageRenderer>(*this, renderer,
- renderer.getLengthLimit());
+ unsigned int rrs_written = BasicRRset::toWire(renderer);
if (getRdataCount() > rrs_written) {
- renderer.setTruncated();
return (rrs_written);
}
if (rrsig_) {
- rrs_written +=
- rrsetToWire<AbstractMessageRenderer>(*(rrsig_.get()), renderer,
- renderer.getLengthLimit());
- }
+ rrs_written += rrsig_->toWire(renderer);
- if (getRdataCount() + getRRsigDataCount() > rrs_written) {
- renderer.setTruncated();
+ if (getRdataCount() + getRRsigDataCount() > rrs_written) {
+ renderer.setTruncated();
+ }
}
return (rrs_written);
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index eb8fa6ed0c..23e1d0a567 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -206,6 +206,20 @@ public:
/// \return The number of \c Rdata objects contained.
virtual unsigned int getRdataCount() const = 0;
+ /// \brief Get the wire format length of the \c AbstractRRset.
+ ///
+ /// This method returns the wire format length of the
+ /// \c AbstractRRset, which is calculated by summing the individual
+ /// lengths of the various fields that make up each RR.
+ ///
+ /// NOTE: When including name lengths, the allocation for
+ /// uncompressed name wire format representation is used.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c AbstractRRset.
+ /// \throw \c EmptyRRset if the \c AbstractRRset is empty.
+ virtual uint16_t getLength() const = 0;
+
/// \brief Returns the owner name of the \c RRset.
///
/// \return A reference to a \c Name class object corresponding to the
@@ -376,10 +390,27 @@ public:
/// Still, this version would offer a more intuitive interface and is
/// provided as such.
///
+ /// NOTE: Because a new Rdata object is constructed, this method can
+ /// throw a std::bad_cast exception if this RRset's class is NONE,
+ /// or if some other error occurs. If you want to be able to add
+ /// RDATA to an RRset whose class is NONE, please use the other
+ /// variant of \c addRdata() which accepts a \c ConstRdataPtr
+ /// argument.
+ ///
/// \param rdata A reference to a \c rdata::RdataPtr (derived) class
/// object, a copy of which is to be added to the \c RRset.
virtual void addRdata(const rdata::Rdata& rdata) = 0;
+ /// \brief Add an RDATA to the RRset (string version).
+ ///
+ /// This method constructs an Rdata object from the the given
+ /// \c rdata_str in presentation format and adds it to the \c RRset.
+ ///
+ /// \param rdata_str RDATA string in presentation format.
+ /// \throw InvalidRdataText if the \c rdata_str is invalid for this
+ /// \c RRset.
+ virtual void addRdata(const std::string& rdata_str) = 0;
+
/// \brief Return an iterator to go through all RDATA stored in the
/// \c RRset.
///
@@ -642,6 +673,13 @@ public:
/// \return The number of \c Rdata objects contained.
virtual unsigned int getRdataCount() const;
+ /// \brief Get the wire format length of the \c BasicRRset.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c BasicRRset.
+ /// \throw \c EmptyRRset if the \c BasicRRset is empty.
+ virtual uint16_t getLength() const;
+
/// \brief Returns the owner name of the \c RRset.
///
/// This method never throws an exception.
@@ -727,6 +765,13 @@ public:
/// See \c AbstractRRset::addRdata(const rdata::Rdata&).
virtual void addRdata(const rdata::Rdata& rdata);
+ /// \brief Add an RDATA to the RRset (string version).
+ ///
+ /// \param rdata_str RDATA string in presentation format.
+ /// \throw InvalidRdataText if the \c rdata_str is invalid for this
+ /// \c RRset.
+ virtual void addRdata(const std::string& rdata_str);
+
/// \brief Return an iterator to go through all RDATA stored in the
/// \c BasicRRset.
///
@@ -813,6 +858,13 @@ public:
virtual ~RRset();
+ /// \brief Get the wire format length of the \c RRset.
+ ///
+ /// \return The length of the wire format representation of the
+ /// \c RRset.
+ /// \throw \c EmptyRRset if the \c RRset is empty.
+ virtual uint16_t getLength() const;
+
/// \brief Render the RRset in the wire format with name compression and
/// truncation handling.
///
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index 73ac2599de..e6ce007bac 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -42,14 +42,15 @@ myIsalpha(char c) {
struct Unit {
char unit;
uint32_t multiply;
+ uint32_t max_allowed;
};
Unit units[] = {
- { 'S', 1 },
- { 'M', 60 },
- { 'H', 60 * 60 },
- { 'D', 24 * 60 * 60 },
- { 'W', 7 * 24 * 60 * 60 }
+ { 'S', 1, 0xffffffff / 1 },
+ { 'M', 60, 0xffffffff / 60 },
+ { 'H', 60 * 60, 0xffffffff / (60 * 60) },
+ { 'D', 24 * 60 * 60, 0xffffffff / (24 * 60 * 60) },
+ { 'W', 7 * 24 * 60 * 60, 0xffffffff / (7 * 24 * 60 * 60) }
};
}
@@ -66,11 +67,9 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
}
return (false);
}
- // We use a larger data type during the computation. This is because
- // some compilers don't fail when out of range, so we check the range
- // ourselves later.
- int64_t val = 0;
+ // We use a larger data type to handle negative number cases.
+ uint64_t val = 0;
const string::const_iterator end = ttlstr.end();
string::const_iterator pos = ttlstr.begin();
@@ -92,7 +91,7 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
} else {
// Case without any units at all. Just convert and store
// it.
- val = boost::lexical_cast<int64_t>(ttlstr);
+ val = boost::lexical_cast<uint64_t>(ttlstr);
break;
}
}
@@ -100,11 +99,13 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
units_mode = true;
// Find the unit and get the size.
uint32_t multiply = 1; // initialize to silence compiler warnings
+ uint32_t max_allowed = 0xffffffff;
bool found = false;
for (size_t i = 0; i < sizeof(units) / sizeof(*units); ++i) {
if (toupper(*unit) == units[i].unit) {
found = true;
multiply = units[i].multiply;
+ max_allowed = units[i].max_allowed;
break;
}
}
@@ -122,15 +123,25 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
}
return (false);
}
- const int64_t value = boost::lexical_cast<int64_t>(string(pos,
- unit));
+ const uint64_t value =
+ boost::lexical_cast<uint64_t>(string(pos, unit));
+ if (value > max_allowed) {
+ if (error_txt != NULL) {
+ *error_txt = "Part of TTL out of range: " + ttlstr;
+ }
+ return (false);
+ }
+
+ // seconds cannot be out of range at this point.
+ const uint64_t seconds = value * multiply;
+ assert(seconds <= 0xffffffff);
+
// Add what we found
- val += multiply * value;
+ val += seconds;
// Check the partial value is still in range (the value can only
// grow, so if we get out of range now, it won't get better, so
// there's no need to continue).
- if (value < 0 || value > 0xffffffff || val < 0 ||
- val > 0xffffffff) {
+ if (val < seconds || val > 0xffffffff) {
if (error_txt != NULL) {
*error_txt = "Part of TTL out of range: " + ttlstr;
}
@@ -146,9 +157,10 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
return (false);
}
- if (val >= 0 && val <= 0xffffffff) {
+ if (val <= 0xffffffff) {
ttlval = val;
} else {
+ // This could be due to negative numbers in input, etc.
if (error_txt != NULL) {
*error_txt = "TTL out of range: " + ttlstr;
}
diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc
index a3ac767b4f..c211a4b897 100644
--- a/src/lib/dns/tests/labelsequence_unittest.cc
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <util/buffer.h>
#include <dns/labelsequence.h>
@@ -853,6 +855,10 @@ TEST_F(LabelSequenceTest, serialize) {
isc::BadValue);
}
+#ifdef ENABLE_DEBUG
+
+// These checks are enabled only in debug mode in the LabelSequence
+// class.
TEST_F(LabelSequenceTest, badDeserialize) {
EXPECT_THROW(LabelSequence(NULL), isc::BadValue);
const uint8_t zero_offsets[] = { 0 };
@@ -879,6 +885,8 @@ TEST_F(LabelSequenceTest, badDeserialize) {
EXPECT_THROW(LabelSequence ls(offsets_noincrease), isc::BadValue);
}
+#endif
+
namespace {
// Helper function; repeatedly calls
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 637357eae4..00b8a5bd5f 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -573,12 +573,10 @@ TEST_F(MessageTest, parseHeader) {
message_parse.endSection(Message::SECTION_ADDITIONAL));
}
-TEST_F(MessageTest, fromWire) {
- // fromWire() isn't allowed in the render mode.
- EXPECT_THROW(factoryFromFile(message_render, "message_fromWire1"),
- InvalidMessageOperation);
-
- factoryFromFile(message_parse, "message_fromWire1");
+void
+checkMessageFromWire(const Message& message_parse,
+ const Name& test_name)
+{
EXPECT_EQ(0x1035, message_parse.getQid());
EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode());
EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode());
@@ -609,6 +607,37 @@ TEST_F(MessageTest, fromWire) {
EXPECT_TRUE(it->isLast());
}
+
+TEST_F(MessageTest, fromWire) {
+ // fromWire() isn't allowed in the render mode.
+ EXPECT_THROW(factoryFromFile(message_render, "message_fromWire1"),
+ InvalidMessageOperation);
+
+ factoryFromFile(message_parse, "message_fromWire1");
+ checkMessageFromWire(message_parse, test_name);
+}
+
+TEST_F(MessageTest, fromWireMultiple) {
+ // Parse from wire multiple times.
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ factoryFromFile(message_parse, "message_fromWire1");
+ checkMessageFromWire(message_parse, test_name);
+
+ // Calling parseHeader() directly before fromWire() should not cause
+ // any problems.
+ received_data.clear();
+ UnitTestUtil::readWireData("message_fromWire1", received_data);
+
+ InputBuffer buffer(&received_data[0], received_data.size());
+ message_parse.parseHeader(buffer);
+ message_parse.fromWire(buffer);
+ message_parse.parseHeader(buffer);
+ message_parse.fromWire(buffer);
+ checkMessageFromWire(message_parse, test_name);
+}
+
TEST_F(MessageTest, fromWireShortBuffer) {
// We trim a valid message (ending with an SOA RR) for one byte.
// fromWire() should throw an exception while parsing the trimmed RR.
@@ -865,7 +894,7 @@ commonTSIGToWireCheck(Message& message, MessageRenderer& renderer,
message.addRRset(Message::SECTION_ANSWER, ans_rrset);
}
- message.toWire(renderer, tsig_ctx);
+ message.toWire(renderer, &tsig_ctx);
vector<unsigned char> expected_data;
UnitTestUtil::readWireData(expected_file, expected_data);
matchWireData(&expected_data[0], expected_data.size(),
@@ -997,7 +1026,7 @@ TEST_F(MessageTest, toWireTSIGTruncation3) {
message_render.addQuestion(Question(Name("www.example.com"),
RRClass::IN(), RRType(i)));
}
- message_render.toWire(renderer, tsig_ctx);
+ message_render.toWire(renderer, &tsig_ctx);
// Check the rendered data by parsing it. We only check it has the
// TC bit on, has the correct number of questions, and has a TSIG RR.
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index 2d075ece97..7c54fad025 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -35,6 +35,45 @@ using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
+
+const uint8_t wiredata_rrsig[] = {
+ // type covered = A
+ 0x00, 0x01,
+ // algorithm = 5
+ 0x05,
+ // labels = 4
+ 0x04,
+ // original TTL = 43200 (0x0000a8c0)
+ 0x00, 0x00, 0xa8, 0xc0,
+ // signature expiration = 1266961577 (0x4b844ca9)
+ 0x4b, 0x84, 0x4c, 0xa9,
+ // signature inception = 1266875177 (0x4b82fb29)
+ 0x4b, 0x82, 0xfb, 0x29,
+ // key tag = 8496 (0x2130)
+ 0x21, 0x30,
+ // signer's name (isc.org.)
+ // 3 i s c 3 o r g 0
+ 0x03, 0x69, 0x73, 0x63, 0x03, 0x6f, 0x72, 0x67, 0x00,
+ // signature data follows
+ 0x7a, 0xfc, 0x61, 0x94, 0x6c,
+ 0x75, 0xde, 0x6a, 0x4a, 0x2d, 0x59, 0x0a, 0xb2,
+ 0x3a, 0x46, 0xcf, 0x27, 0x12, 0xe6, 0xdc, 0x2d,
+ 0x22, 0x8c, 0x4e, 0x9a, 0x53, 0x75, 0xe3, 0x0f,
+ 0x6d, 0xe4, 0x08, 0x33, 0x18, 0x19, 0xb3, 0x76,
+ 0x21, 0x9d, 0x2c, 0x8a, 0xc5, 0x69, 0xba, 0xab,
+ 0xef, 0x66, 0x9f, 0xda, 0xb5, 0x2a, 0xf9, 0x40,
+ 0xc1, 0x28, 0xc5, 0x97, 0xba, 0x3c, 0x19, 0x4d,
+ 0x95, 0x13, 0xc2, 0xcd, 0xf6, 0xb1, 0x59, 0x5d,
+ 0x0c, 0xf9, 0x3f, 0x35, 0xbb, 0x9a, 0x70, 0x93,
+ 0x36, 0xe5, 0xf4, 0x17, 0x7e, 0xfe, 0x66, 0x3b,
+ 0x70, 0x1f, 0xed, 0x33, 0xa8, 0xa3, 0x0d, 0xc0,
+ 0x8c, 0xc6, 0x95, 0x1b, 0xd8, 0x9c, 0x8c, 0x25,
+ 0xb4, 0x57, 0x9e, 0x56, 0x71, 0x64, 0x14, 0x7f,
+ 0x8f, 0x6d, 0xfa, 0xc5, 0xca, 0x3f, 0x36, 0xe2,
+ 0xa4, 0xdf, 0x60, 0xfa, 0xcd, 0x59, 0x3e, 0x22,
+ 0x32, 0xa1, 0xf7
+};
+
class Rdata_RRSIG_Test : public RdataTest {
protected:
Rdata_RRSIG_Test() :
@@ -299,13 +338,19 @@ TEST_F(Rdata_RRSIG_Test, createFromLexer) {
}
TEST_F(Rdata_RRSIG_Test, toWireRenderer) {
- // FIXME: This doesn't check the result.
rdata_rrsig.toWire(renderer);
+
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ renderer.getData(), renderer.getLength(),
+ wiredata_rrsig, sizeof(wiredata_rrsig));
}
TEST_F(Rdata_RRSIG_Test, toWireBuffer) {
- // FIXME: This doesn't check the result.
rdata_rrsig.toWire(obuffer);
+
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ wiredata_rrsig, sizeof(wiredata_rrsig));
}
TEST_F(Rdata_RRSIG_Test, createFromWire) {
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index 99ce810ab5..cb8640af1a 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -62,11 +62,6 @@ protected:
rdata_str, rdata_sshfp, true, true);
}
- void checkFromText_BadValue(const string& rdata_str) {
- checkFromText<generic::SSHFP, InvalidRdataText, BadValue>(
- rdata_str, rdata_sshfp, true, true);
- }
-
void checkFromText_BadString(const string& rdata_str) {
checkFromText
<generic::SSHFP, InvalidRdataText, isc::Exception>(
@@ -141,8 +136,8 @@ TEST_F(Rdata_SSHFP_Test, badText) {
checkFromText_LexerError("1");
checkFromText_LexerError("ONE 2 123456789abcdef67890123456789abcdef67890");
checkFromText_LexerError("1 TWO 123456789abcdef67890123456789abcdef67890");
- checkFromText_BadValue("1 2 BUCKLEMYSHOE");
- checkFromText_BadValue(sshfp_txt + " extra text");
+ checkFromText_InvalidText("1 2 BUCKLEMYSHOE");
+ checkFromText_InvalidText(sshfp_txt + " extra text");
// yes, these are redundant to the last test cases in algorithmTypes
checkFromText_InvalidText(
@@ -235,7 +230,8 @@ TEST_F(Rdata_SSHFP_Test, toWire) {
this->obuffer.clear();
rdata_sshfp.toWire(this->obuffer);
- EXPECT_EQ(22, this->obuffer.getLength());
+ EXPECT_EQ(sizeof (rdata_sshfp_wiredata),
+ this->obuffer.getLength());
matchWireData(rdata_sshfp_wiredata, sizeof(rdata_sshfp_wiredata),
obuffer.getData(), obuffer.getLength());
@@ -255,8 +251,20 @@ TEST_F(Rdata_SSHFP_Test, getFingerprintType) {
EXPECT_EQ(1, rdata_sshfp.getFingerprintType());
}
-TEST_F(Rdata_SSHFP_Test, getFingerprintLen) {
- EXPECT_EQ(20, rdata_sshfp.getFingerprintLen());
+TEST_F(Rdata_SSHFP_Test, getFingerprint) {
+ const std::vector<uint8_t>& fingerprint =
+ rdata_sshfp.getFingerprint();
+
+ EXPECT_EQ(rdata_sshfp.getFingerprintLength(),
+ fingerprint.size());
+ for (int i = 0; i < fingerprint.size(); ++i) {
+ EXPECT_EQ(rdata_sshfp_wiredata[i + 2],
+ fingerprint.at(i));
+ }
+}
+
+TEST_F(Rdata_SSHFP_Test, getFingerprintLength) {
+ EXPECT_EQ(20, rdata_sshfp.getFingerprintLength());
}
TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromWire) {
@@ -274,7 +282,7 @@ TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromWire) {
EXPECT_EQ(4, rdf.getAlgorithmNumber());
EXPECT_EQ(9, rdf.getFingerprintType());
- EXPECT_EQ(0, rdf.getFingerprintLen());
+ EXPECT_EQ(0, rdf.getFingerprintLength());
this->obuffer.clear();
rdf.toWire(this->obuffer);
@@ -296,7 +304,7 @@ TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromString) {
EXPECT_EQ(5, rdata_sshfp2.getAlgorithmNumber());
EXPECT_EQ(6, rdata_sshfp2.getFingerprintType());
- EXPECT_EQ(0, rdata_sshfp2.getFingerprintLen());
+ EXPECT_EQ(0, rdata_sshfp2.getFingerprintLength());
this->obuffer.clear();
rdata_sshfp2.toWire(this->obuffer);
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 9739325014..f2810ac857 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -214,6 +214,14 @@ TEST_F(RdataTest, createRdataWithLexer) {
"file does not end with newline");
}
+TEST_F(RdataTest, getLength) {
+ const in::AAAA aaaa_rdata("2001:db8::1");
+ EXPECT_EQ(16, aaaa_rdata.getLength());
+
+ const generic::TXT txt_rdata("Hello World");
+ EXPECT_EQ(12, txt_rdata.getLength());
+}
+
}
}
}
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index 66621b9a15..fc75b7731c 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -131,7 +131,7 @@ TEST_F(RRsetTest, isSameKind) {
void
addRdataTestCommon(const RRset& rrset) {
- EXPECT_EQ(2, rrset.getRdataCount());
+ ASSERT_EQ(2, rrset.getRdataCount());
RdataIteratorPtr it = rrset.getRdataIterator(); // cursor is set to the 1st
EXPECT_FALSE(it->isLast());
@@ -158,14 +158,34 @@ TEST_F(RRsetTest, addRdataPtr) {
rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(),
rrset_a_empty.getClass(),
"192.0.2.2"));
+ addRdataTestCommon(rrset_a_empty);
+}
- addRdataTestCommon(rrset_a);
-
+TEST_F(RRsetTest, addRdataPtrMismatched) {
// Pointer version of addRdata() doesn't type check and does allow to
//add a different type of Rdata as a result.
+
+ // Type mismatch
rrset_a_empty.addRdata(createRdata(RRType::NS(), RRClass::IN(),
"ns.example.com."));
- EXPECT_EQ(3, rrset_a_empty.getRdataCount());
+ EXPECT_EQ(1, rrset_a_empty.getRdataCount());
+
+ // Class mismatch
+ rrset_ch_txt.addRdata(createRdata(RRType::TXT(), RRClass::IN(),
+ "Test String"));
+ EXPECT_EQ(1, rrset_ch_txt.getRdataCount());
+}
+
+TEST_F(RRsetTest, addRdataString) {
+ rrset_a_empty.addRdata("192.0.2.1");
+ rrset_a_empty.addRdata("192.0.2.2");
+
+ addRdataTestCommon(rrset_a_empty);
+
+ // String version of addRdata() will throw for bad RDATA for
+ // RRType::A().
+ EXPECT_THROW(rrset_a_empty.addRdata("ns.example.com."), InvalidRdataText);
+ addRdataTestCommon(rrset_a_empty);
}
TEST_F(RRsetTest, iterator) {
@@ -205,6 +225,30 @@ TEST_F(RRsetTest, toText) {
rrset_none_a_empty.toText());
}
+TEST_F(RRsetTest, getLength) {
+ // Empty RRset should throw
+ EXPECT_THROW(rrset_a_empty.getLength(), EmptyRRset);
+
+ // Unless it is type ANY or NONE:
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // Total = 18 + 2 + 2 + 4 + 2 = 28 octets
+ EXPECT_EQ(28, rrset_any_a_empty.getLength());
+ EXPECT_EQ(28, rrset_none_a_empty.getLength());
+
+ // RRset with single RDATA
+ // 28 (above) + 4 octets (A RDATA) = 32 octets
+ rrset_a_empty.addRdata(in::A("192.0.2.1"));
+ EXPECT_EQ(32, rrset_a_empty.getLength());
+
+ // 2 A RRs
+ rrset_a_empty.addRdata(in::A("192.0.2.2"));
+ EXPECT_EQ(32 + 32, rrset_a_empty.getLength());
+}
+
TEST_F(RRsetTest, toWireBuffer) {
rrset_a.toWire(buffer);
@@ -212,11 +256,13 @@ TEST_F(RRsetTest, toWireBuffer) {
matchWireData(&wiredata[0], wiredata.size(),
buffer.getData(), buffer.getLength());
- // toWire() cannot be performed for an empty RRset.
+ // toWire() cannot be performed for an empty RRset except when
+ // class=ANY or class=NONE.
buffer.clear();
EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset);
- // Unless it is type ANY or None
+ // When class=ANY or class=NONE, toWire() can also be performed for
+ // an empty RRset.
buffer.clear();
rrset_any_a_empty.toWire(buffer);
wiredata.clear();
@@ -242,25 +288,26 @@ TEST_F(RRsetTest, toWireRenderer) {
matchWireData(&wiredata[0], wiredata.size(),
renderer.getData(), renderer.getLength());
- // toWire() cannot be performed for an empty RRset.
- buffer.clear();
- EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset);
+ // toWire() cannot be performed for an empty RRset except when
+ // class=ANY or class=NONE.
+ renderer.clear();
+ EXPECT_THROW(rrset_a_empty.toWire(renderer), EmptyRRset);
- // Unless it is type ANY or None
- // toWire() can also be performed for an empty RRset.
- buffer.clear();
- rrset_any_a_empty.toWire(buffer);
+ // When class=ANY or class=NONE, toWire() can also be performed for
+ // an empty RRset.
+ renderer.clear();
+ rrset_any_a_empty.toWire(renderer);
wiredata.clear();
UnitTestUtil::readWireData("rrset_toWire3", wiredata);
matchWireData(&wiredata[0], wiredata.size(),
- buffer.getData(), buffer.getLength());
+ renderer.getData(), renderer.getLength());
- buffer.clear();
- rrset_none_a_empty.toWire(buffer);
+ renderer.clear();
+ rrset_none_a_empty.toWire(renderer);
wiredata.clear();
UnitTestUtil::readWireData("rrset_toWire4", wiredata);
matchWireData(&wiredata[0], wiredata.size(),
- buffer.getData(), buffer.getLength());
+ renderer.getData(), renderer.getLength());
}
// test operator<<. We simply confirm it appends the result of toText().
@@ -364,4 +411,38 @@ TEST_F(RRsetRRSIGTest, toText) {
"20100322084538 20100220084538 1 example.com. FAKEFAKEFAKEFAKE\n",
rrset_aaaa->toText());
}
+
+TEST_F(RRsetRRSIGTest, getLength) {
+ // A RR
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // A RDATA = 4 octets
+ // Total = 18 + 2 + 2 + 4 + 2 + 4 = 32 octets
+
+ // 2 A RRs
+ EXPECT_EQ(32 + 32, rrset_a->getLength());
+
+ // RRSIG
+ // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets
+ // TYPE field = 2 octets
+ // CLASS field = 2 octets
+ // TTL field = 4 octets
+ // RDLENGTH field = 2 octets
+ // RRSIG RDATA = 40 octets
+ // Total = 18 + 2 + 2 + 4 + 2 + 40 = 68 octets
+ RRsetPtr my_rrsig(new RRset(test_name, RRClass::IN(),
+ RRType::RRSIG(), RRTTL(3600)));
+ my_rrsig->addRdata(generic::RRSIG("A 4 3 3600 "
+ "20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE"));
+ EXPECT_EQ(68, my_rrsig->getLength());
+
+ // RRset with attached RRSIG
+ rrset_a->addRRsig(my_rrsig);
+
+ EXPECT_EQ(32 + 32 + 68, rrset_a->getLength());
+}
}
diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
index 40a58c0e2c..6348e9cbb5 100644
--- a/src/lib/dns/tests/tsig_unittest.cc
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -190,7 +190,7 @@ TSIGTest::createMessageAndSign(uint16_t id, const Name& qname,
(ctx->getState() == TSIGContext::INIT) ?
TSIGContext::SENT_REQUEST : TSIGContext::SENT_RESPONSE;
- message.toWire(renderer, *ctx);
+ message.toWire(renderer, ctx);
message.clear(Message::PARSE);
InputBuffer buffer(renderer.getData(), renderer.getLength());
diff --git a/src/lib/hooks/.gitignore b/src/lib/hooks/.gitignore
index 5a9364c623..89adbe1baf 100644
--- a/src/lib/hooks/.gitignore
+++ b/src/lib/hooks/.gitignore
@@ -1,2 +1,3 @@
/hooks_messages.cc
/hooks_messages.h
+/s-messages
diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am
index 36b62871cf..65153e2660 100644
--- a/src/lib/hooks/tests/Makefile.am
+++ b/src/lib/hooks/tests/Makefile.am
@@ -36,6 +36,7 @@ if HAVE_GTEST
# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
# Use of these switches will guarantee that the .so files are created in the
# .libs folder and they can be dlopened.
+#
# Note that the shared libraries with callouts should not be used together with
# the --enable-static-link option. With this option, the bind10 libraries are
# statically linked with the program and if the callout invokes the methods
@@ -44,8 +45,9 @@ if HAVE_GTEST
# to unexpected errors. For this reason, the --enable-static-link option is
# ignored for unit tests built here.
-lib_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la liblecl.la \
- libucl.la libfcl.la
+nodistdir=$(abs_top_builddir)/src/lib/hooks/tests
+nodist_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la \
+ liblecl.la libucl.la libfcl.la
# No version function
libnvl_la_SOURCES = no_version_library.cc
@@ -124,14 +126,16 @@ run_unittests_LDADD = $(AM_LDADD) $(GTEST_LDADD)
run_unittests_LDADD += $(ALL_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-# As noted in configure.ac, libtool doesn't work perfectly with Darwin: it embeds the
-# final install path in dynamic libraries and loadable modules refer to that path even
-# if its loaded within the source tree, so preventing tests from working - but only
-# when linking statically. The solution used in other Makefiles (setting the path
-# to the dynamic libraries via an environment variable) don't seem to work. What does
-# work is to run the unit test using libtool and specifying paths via -dlopen switches.
-# So... If running in an environment where we have to set the library path AND if
-# linking statically, override the "check" target and run the unit tests ourselves.
+# As noted in configure.ac, libtool doesn't work perfectly with Darwin: it
+# embeds the final install path in dynamic libraries and loadable modules refer
+# to that path even if its loaded within the source tree, so preventing tests
+# from working - but only when linking statically. The solution used in other
+# Makefiles (setting the path to the dynamic libraries via an environment
+# variable) don't seem to work. What does work is to run the unit test using
+# libtool and specifying paths via -dlopen switches. So... If running in an
+# environment where we have to set the library path AND if linking statically,
+# override the "check" target and run the unit tests ourselves.
+
if USE_STATIC_LINK
if SET_ENV_LIBRARY_PATH
check-TESTS:
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 0bd1b05d9a..04b90d16a8 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -32,7 +32,7 @@ libb10_log_la_SOURCES += message_types.h
libb10_log_la_SOURCES += output_option.cc output_option.h
libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
-EXTRA_DIST = README
+EXTRA_DIST = logging.dox
EXTRA_DIST += logimpl_messages.mes
EXTRA_DIST += log_messages.mes
diff --git a/src/lib/log/README b/src/lib/log/README
deleted file mode 100644
index 2a7af28845..0000000000
--- a/src/lib/log/README
+++ /dev/null
@@ -1,529 +0,0 @@
-This directory holds the first release of the logging system.
-
-
-Basic Ideas
-===========
-The BIND-10 logging system merges two ideas:
-
-* A hierarchical logging system similar to that used in Java (i.e. log4j)
-* Separation of message use from message text
-
-
-Hierarchical Logging System
-===========================
-When a program writes a message to the logging system, it does so using an
-instance of the Logger class. As well as performing the write of the message,
-the logger identifies the source of the message: different sources can write
-to different destinations and can log different severities of messages.
-For example, the "cache" logger could write messages of DEBUG severity or
-above to a file while all other components write messages of "INFO" severity
-or above to the Syslog file.
-
-The loggers are hierarchical in that each logger is the child of another
-logger. The top of the hierarchy is the root logger, which does not have
-a parent. The point of the hierarchy is that unless a logger is explicitly
-assigned an attribute (such as severity of message being logger), it picks
-it up from the parent. (In BIND-10, there is the root logger (named after
-the program) and every other logger is a child of that.) So in the example
-above, the INFO/Syslog attributes could be associated with the root logger
-while the DEBUG/file attributes are associated with the "cache" logger.
-
-
-Separation of Messages Use from Message Text
-============================================
-By separating the use of the message from the text associated with this -
-in essence, defining message text in an external file - it is possible to
-replace the supplied text of the messages with a local language version.
-
-Each message is identified by an identifier e.g. "LOG_WRITE_ERROR".
-Within the program, this is the symbol passed to the logging system.
-The logger system uses the symbol as an index into a dictionary to
-retrieve the message associated with it (e.g. "unable to open %s for
-input"). It then substitutes any message parameters (in this example,
-the name of the file where the write operation failed) and logs it to
-the destination.
-
-In BIND-10, a the default text for each message is linked into the
-program. Each program is able to read a locally-defined message file
-when it starts, updating the stored definitions with site-specific text.
-When the message is logged, the updated text is output. However, the
-message identifier is always included in the output so that the origin
-of the message can be identified even if the text has been changed.
-
-
-Using The System
-================
-The steps in using the system are:
-
-1. Create a message file. This defines messages by an identification - a
- mnemonic for the message, the convention being that these are a few
- words separated by underscores - and text that explains the message in
- more detail. The file is described in more detail below.
-
- Ideally the file should have a file type of ".mes".
-
-2. Run it through the message compiler to produce the .h and .cc files. This
- step should be included in the build process. (For an example of how to
- do this, see src/lib/nsas/Makefile.am.) During development though, the
- message compiler (found in the "src/lib/log/compiler" directory) will need
- to be run manually. The only argument is the name of the message file: it
- will produce as output two files in the default directory, having the same
- name as the input file but with file types of ".h" and ".cc".
-
-3. Include the .h file in your source code to define message symbols, and
- make sure that the .cc file is compiled and linked into your program -
- static initialization will add the symbols to the global dictionary.
-
-4. Declare loggers in your code and use them to log messages. This is
- described in more detail below.
-
-5. To set the debug level and run-time message file, call initLogger (declared
- in logger_support.h) in the main program unit.
-
-
-Message Files
-=============
-
-File Contents and Format
-------------------------
-A message file is a file containing message definitions. Typically there
-will be one message file for each component that declares message symbols.
-An example file could be:
-
--- BEGIN --
-
-# Example message file
-
-$NAMESPACE isc::log
-
-% LOG_UNRECOGNISED_DIRECTIVE line %1: unrecognised directive '%2'
-A line starting with a dollar symbol was found, but the first word on the line
-(shown in the message) was not a recognised message compiler directive.
-
-% LOG_WRITE_ERROR error writing to %1: %2
-The specified error was encountered by the message compiler when writing to
-the named output file.
-
--- END --
-
-Points to note:
-* Leading and trailing space are trimmed from the line. Although the above
- example has every line starting at column 1, the lines could be indented
- if desired.
-
-* Blank lines are ignored.
-
-* Lines starting with "#" are comments are are ignored. Comments must be on
- a line by themselves - inline comments will be interpreted as part of the
- text of the line.
-
-* Lines starting $ are directives. At present, just one directive is
- recognised:
-
- * $NAMESPACE, which has one argument: the namespace in which the symbols are
- created. In the absence of a $NAMESPACE directive, symbols will be put in
- the anonymous namespace.
-
-* Message lines. These start with a "%" and are followed by the message
- identification and the message text, the latter including zero or more
- replacement tokens, e.g.
-
- % LOG_WRITE_ERROR error writing to %1: %2
-
- * There may be zero or more spaces between the leading "%" and the message
- identification (which, in the example above, is the string
- "LOG_WRITE_ERROR").
-
- * The message identification can be any string of letters, digits and
- underscores, but should not start with a digit. The convention adopted
- in BIND 10 is for the first component (before the first underscore) to be
- a string indicating the origin of the message, and the remainder to
- describe the message. So in the example above, the LOG_ indicates that
- the error originated from the logging library and the "WRITE_ERROR"
- indicates that there was a problem in a write operation.
-
- * The rest of the line - from the first non-space character to the
- last non- space character - is taken exactly for the text
- of the message. There are no restrictions on what characters may
- be in this text, other than they be printable. (This means that
- both single-quote (') and double-quote (") characters are allowed.)
- The message text may include replacement tokens (the strings "%1",
- "%2" etc.). When a message is logged, these are replaced with the
- arguments passed to the logging call: %1 refers to the first argument,
- %2 to the second etc. Within the message text, the placeholders
- can appear in any order and placeholders can be repeated. Otherwise,
- the message is printed unmodified.
-
-* Remaining lines indicate an explanation for the preceding message. These
- are intended to be processed by a separate program and used to generate
- an error messages manual. They are ignored by the message compiler.
-
-Message Compiler
-----------------
-The message compiler is a program built in the src/log/compiler directory.
-It is invoked by the command:
-
- message [-h] [-v] -p] <message-file>
-
-("-v" prints the version number and exits; "-h" prints brief help text.) The
-compiler produces source files for C++ and Python.
-
-C++ Files
----------
-Without the "-p" option, the message compiler processes the message file
-to produce two files:
-
-1) A C++ header file (called <message-file-name>.h) that holds lines of
-the form:
-
- namespace <namespace> {
- extern const isc::log::MessageID LOG_WRITE_ERROR;
- :
- }
-
-The symbols define the keys in the global message dictionary, with the
-namespace enclosing the symbols set by the $NAMESPACE directive.
-
-(This is the reason for the restriction on message identifiers - they
-have to be valid C++ symbol names.)
-
-2) A C++ source file (called <message-file-name>.cc) that holds the definitions
-of the global symbols and code to insert the symbols and messages into the map.
-
-Symbols are defined to be equal to strings holding the identifier, e.g.
-
- extern const isc::log::MessageID LOG_WRITE_ERROR = "LOG_WRITE_ERROR";
-
-(The implementation allows symbols to be compared. However, use of strings
-should not be assumed - a future implementation may change this.)
-
-In addition, the file declares an array of identifiers/messages and an object
-to add them to the global dictionary:
-
- namespace {
- const char* values[] = {
- identifier1, text1,
- identifier2, text2,
- :
- NULL
- };
-
- const isc::log::MessageInitializer initializer(values);
- }
-
-The constructor of the MessageInitializer object retrieves the singleton
-global Dictionary object (created using standard methods to avoid the
-"static initialization fiasco") and adds each identifier and text to it.
-A check is made as each is added; if the identifier already exists, it is
-added to "overflow" vector; the vector is printed to the main logging output
-when logging is finally enabled (to indicate a programming error).
-
-Python Files
-------------
-If the "-p" option is given, the compiler produces a Python module defining
-the messages. The format of this is:
-
-import isc.log
- :
-LOG_WRITE_ERROR = isc.log.create_message("LOG_WRITE_ERROR",
- "error writing to %1 : %2")
-
-(The definition is output on one line - it is split across two lines in this
-document for readability.)
-
-The module can be imported into other Python code, and messages logged
-in a similar way to C++ using the Python logging library.
-
-Using the Logging - C++
-=======================
-1. Build message header file and source file as describe above.
-
-2. The main program unit must include a call to isc::log::initLogger()
- (described in more detail below) to set the logging severity, debug log
- level, and external message file:
-
- a) The logging severity is one of the enum defined in logger.h, i.e.
-
- isc::log::DEBUG
- isc::log::INFO
- isc::log::WARN
- isc::log::ERROR
- isc::log::FATAL
- isc::log::NONE
-
- b) The debug log level is only interpreted when the severity is
- DEBUG and is an integer ranging from 0 to 99. 0 should be used
- for the highest-level debug messages and 99 for the lowest-level
- (and typically more verbose) messages.
-
- c) The external message file. If present, this is the same as a
- standard message file, although it should not include any
- directives. (A single directive of a particular type will be
- ignored; multiple directives will cause the read of the file to
- fail with an error.)
-
- The settings remain in effect until the logging configuration is read,
- and so provide the default logging during program initialization.
-
-3. Declare a logger through which the message will be logged.
-
- isc::log::Logger logger("name");
-
- The string passed to the constructor is the name of the logger (it
- can be any string) and is used when configuring it. Loggers with
- the same name share the same configuration.
-
-4. Issue logging calls using supplied macros in "log/macros.h", e.g.
-
- LOG_ERROR(logger, LOG_WRITE_ERROR).arg("output.txt");
-
- (The macros are more efficient that calls to the methods on the logger
- class: they avoid the overhead of evaluating the parameters to arg()
- if the settings are such that the message is not going to be output.)
-
-Using the Logging - Python
-==========================
-1. Build message module as describe above.
-
-2. The main program unit must include a call to isc.log.init()
- (described in more detail below) to set the to set the logging
- severity, debug log level, and external message file:
-
- a) The logging severity is one of the strings:
-
- DEBUG
- INFO
- WARN
- ERROR
- FATAL
- NONE
-
- b) The debug log level is only interpreted when the severity is
- DEBUG and is an integer ranging from 0 to 99. 0 should be used
- for the highest-level debug messages and 99 for the lowest-level
- (and typically more verbose) messages.
-
- c) The external message file. If present, this is the same as a
- standard message file, although it should not include any
- directives. (Any that are there will be ignored.)
-
- The settings remain in effect until the logging configuration is read,
- and so provide the default logging during program initialization.
-
-3. Declare a logger through which the message will be logged.
-
- isc.log.Logger logger("name")
-
- The string passed to the constructor is the name of the logger (it
- can be any string) and is used when configuring it. Loggers with
- the same name share the same configuration.
-
-4. Issue calls to the logging methods:
-
- logger.error(LOG_WRITE_ERROR, "output.txt");
-
-Logging Initialization
-======================
-In all cases, if an attempt is made to use a logging method before the logging
-has been initialized, the program will terminate with a LoggingNotInitialized
-exception.
-
-C++
----
-Logging Initialization is carried out by calling initLogger(). There are two
-variants to the call, one for use by production programs and one for use by
-unit tests.
-
-Variant #1, Used by Production Programs
----------------------------------------
-void isc::log::initLogger(const std::string& root,
- isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL,
- bool buffer = false);
-
-This is the call that should be used by production programs:
-
-root
-Name of the program (e.g. "b10-auth"). This is also the name of the root
-logger and is used when configuring logging.
-
-severity
-Default severity that the program will start logging with. Although this may
-be overridden when the program obtains its configuration from the configuration
-database, this is the severity that it used until then. (This may be set by
-a command-line parameter.)
-
-dbglevel
-The debug level used if "severity" is set to isc::log::DEBUG.
-
-file
-The name of a local message file. This will be read and its definitions used
-to replace the compiled-in text of the messages.
-
-buffer
-If set to true, initial log messages will be internally buffered, until the
-first time a logger specification is processed. This way the program can
-use logging before even processing its logging configuration. As soon as any
-specification is processed (even an empty one), the buffered log messages will
-be flushed according to the specification. Note that if this option is used,
-the program SHOULD call one of the LoggerManager's process() calls (if you
-are using the built-in logging configuration handling in ModuleCCSession,
-this is automatically handled). If the program exits before this is done,
-all log messages are dumped in a raw format to stdout (so that no messages
-get lost).
-
-Variant #2, Used by Unit Tests
-------------------------------
- void isc::log::initLogger()
-
-This is the call that should be used by unit tests. In this variant, all the
-options are supplied by environment variables. (It should not be used for
-production programs to avoid the chance that the program operation is affected
-by inadvertently-defined environment variables.)
-
-The environment variables are:
-
-B10_LOGGER_ROOT
-Sets the "root" for the unit test. If not defined, the name "bind10" is used.
-
-B10_LOGGER_SEVERITY
-The severity to set for the root logger in the unit test. Valid values are
-"DEBUG", "INFO", "WARN", "ERROR", "FATAL" and "NONE". If not defined, "INFO"
-is used.
-
-B10_LOGGER_DBGLEVEL
-If B10_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can be a
-number between 0 and 99, and defaults to 0.
-
-B10_LOGGER_LOCALMSG
-If defined, points to a local message file. The default is not to use a local
-message file.
-
-B10_LOGGER_DESTINATION
-The location to which log message are written. This can be one of:
-
- stdout Message are written to stdout
- stderr Messages are written to stderr
- syslog[:facility] Messages are written to syslog. If the optional
- "facility" is used, the messages are written using
- that facility. (This defaults to "local0" if not
- specified.)
- Anything else Interpreted as the name of a file to which output
- is appended. If the file does not exist, a new one
- is opened.
-
-In the case of "stdout", "stderr" and "syslog", they must be written exactly
-as is - no leading or trailing spaces, and in lower-case.
-
-Python
-------
-To be supplied
-
-
-Severity Guidelines
-===================
-When using logging, the question arises, what severity should a message
-be logged at? The following is a suggestion - as always, the decision
-must be made in the context of which the message is logged.
-
-One thing that should always be borne in mind is whether the logging
-could be used as a vector for a DOS attack. For example, if a warning
-message is logged every time an invalid packet is received, an attacker
-could simply send large numbers of invalid packets. (Of course, warnings
-could be disabled (or just warnings for that that particular logger),
-but nevertheless the message is an attack vector.)
-
-FATAL
------
-The program has encountered an error that is so severe that it cannot
-continue (or there is no point in continuing). When a fatal error
-has been logged, the program will usually exit immediately (or shortly
-afterwards) after dumping some diagnostic information.
-
-ERROR
------
-Something has happened such that the program can continue but the
-results for the current (or future) operations cannot be guaranteed to
-be correct, or the results will be correct but the service is impaired.
-For example, the program started but attempts to open one or more network
-interfaces failed.
-
-WARN
-----
-An unusual event happened. Although the program will continue working
-normally, the event was sufficiently out of the ordinary to warrant
-drawing attention to it. For example, at program start-up a zone was
-loaded that contained no resource records,
-
-INFO
-----
-A normal but significant event has occurred that should be recorded,
-e.g. the program has started or is just about to terminate, a new zone
-has been created, etc.
-
-DEBUG
------
-This severity is only enabled on for debugging purposes. A debug level is
-associated with debug messages, level 0 (the default) being for high-level
-messages and level 99 (the maximum) for the lowest level. How the
-messages are distributed between the levels is up to the developer.
-So if debugging the NSAS (for example), a level 0 message might record
-the creation of a new zone, a level 10 recording a timeout when trying
-to get a nameserver address, but a level 50 would record every query for
-an address. (And we might add level 70 to record every update of the RTT.)
-
-Note that like severities, levels are cumulative; so if level 25 is
-set as the debug level, all debug levels from 0 to 25 will be output.
-In fact, it is probably easier to visualise the debug levels as part of
-the severity system:
-
- FATAL High
- ERROR
- WARN
- INFO
- DEBUG level 0
- DEBUG level 1
- :
- DEBUG level 99 Low
-
-When a particular severity is set, it - and all severities and/or debug
-levels above it - will be logged.
-
-To try to ensure that the information from different modules is roughly
-comparable for the same debug level, a set of standard debug levels has
-been defined for common type of debug output. However, modules are free
-to set their own debug levels or define additional ones.
-
-Logging Sources v Logging Severities
-------------------------------------
-When logging events, make a distinction between events related to the
-server and events related to DNS messages received. Caution needs to
-be exercised with the latter as, if the logging is enabled in the normal
-course of events, such logging could be a denial of service vector. For
-example, suppose that the main authoritative service logger were to
-log both zone loading and unloading as INFO and a warning message if
-it received an invalid packet. An attacker could make the INFO messages
-unusable by flooding the server with malformed packets.
-
-There are two approaches to get round this:
-
-a) Make the logging of packet-dependent events a DEBUG-severity message.
-DEBUG is not enabled by default, so these events will not be recorded
-unless DEBUG is specifically chosen.
-
-b) Record system-related and packet-related messages via different loggers
-(e.g. in the example given, server events could be logged using the
-logger "auth" and packet-related events at that level logged using the
-logger "pkt-auth".) As the loggers are independent and the severity
-levels independent, fine-tuning of what and what is not recorded can
-be achieved.
-
-
-Notes
-=====
-The message compiler is written in C++ (instead of Python) because it
-contains a component that reads the message file. This component is used
-in both the message compiler and the server; in the server it is used
-when the server starts up (or when triggered by a command) to read in
-a message file to overwrite the internal dictionary. Writing it in C++
-means there is only one piece of code that does this functionality.
diff --git a/src/lib/log/buffer_appender_impl.cc b/src/lib/log/buffer_appender_impl.cc
index 8899c4f7cf..4aa222f3a2 100644
--- a/src/lib/log/buffer_appender_impl.cc
+++ b/src/lib/log/buffer_appender_impl.cc
@@ -30,6 +30,7 @@ BufferAppender::~BufferAppender() {
// So dump all that is left to stdout
try {
flushStdout();
+ destructorImpl();
} catch (...) {
// Ok if we can't even seem to dump to stdout, never mind.
}
diff --git a/src/lib/log/interprocess/interprocess_sync_file.h b/src/lib/log/interprocess/interprocess_sync_file.h
index cb070038c4..454cd8bd8d 100644
--- a/src/lib/log/interprocess/interprocess_sync_file.h
+++ b/src/lib/log/interprocess/interprocess_sync_file.h
@@ -53,7 +53,7 @@ public:
///
/// Creates a file-based interprocess synchronization object
///
- /// \param name Name of the synchronization task. This has to be
+ /// \param task_name Name of the synchronization task. This has to be
/// identical among the various processes that need to be
/// synchronized for the same task.
InterprocessSyncFile(const std::string& task_name) :
diff --git a/src/lib/log/interprocess/interprocess_sync_null.h b/src/lib/log/interprocess/interprocess_sync_null.h
index 41dab50143..f046f79400 100644
--- a/src/lib/log/interprocess/interprocess_sync_null.h
+++ b/src/lib/log/interprocess/interprocess_sync_null.h
@@ -32,7 +32,7 @@ public:
///
/// Creates a null interprocess synchronization object
///
- /// \param name Name of the synchronization task. This has to be
+ /// \param task_name Name of the synchronization task. This has to be
/// identical among the various processes that need to be
/// synchronized for the same task.
InterprocessSyncNull(const std::string& task_name) :
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
index 8cb34c7314..d336b30973 100644
--- a/src/lib/log/log_formatter.h
+++ b/src/lib/log/log_formatter.h
@@ -163,8 +163,12 @@ public:
/// This is the place where output happens if the formatter is active.
~ Formatter() {
if (logger_) {
- checkExcessPlaceholders(message_, ++nextPlaceholder_);
- logger_->output(severity_, *message_);
+ try {
+ checkExcessPlaceholders(message_, ++nextPlaceholder_);
+ logger_->output(severity_, *message_);
+ } catch (...) {
+ // Catch and ignore all exceptions here.
+ }
delete message_;
}
}
diff --git a/src/lib/log/logging.dox b/src/lib/log/logging.dox
new file mode 100644
index 0000000000..f4d022ca1c
--- /dev/null
+++ b/src/lib/log/logging.dox
@@ -0,0 +1,762 @@
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Note: the prefix "log" to all labels is an abbreviation for "Logging"
+// and is used to prevent a clash with symbols in any other Doxygen file.
+
+/**
+@page logBind10Logging BIND 10 Logging
+
+@section logBasicIdeas Basic Ideas
+
+The BIND 10 logging system is based on the log4J logging system
+common in Java development, and includes the following ideas:
+
+- A set of severity levels.
+- A hierarchy of logging sources.
+- Separation of message use from message text.
+
+@subsection logSeverity Message Severity
+Each message logged by BIND 10 has a severity associated with it, ranging
+from FATAL - the most severe - to DEBUG - messages output as the code
+executes to facilitate debugging. In order of decreasing severity,
+the levels are:
+
+<dl>
+<dt>FATAL</dt>
+<dd>The program has encountered an error that is so severe that it
+cannot continue (or there is no point in continuing). For example, an
+unhandled exception generated deep within the code has been caught by the
+top-level program. When a fatal error has been logged, the program will
+exit immediately (or shortly afterwards) after dumping some diagnostic
+information.</dd>
+
+<dt>ERROR</dt>
+<dd>Something has happened such that the program can continue but the
+results for the current (or future) operations cannot be guaranteed to
+be correct, or the results will be correct but the service is impaired.
+For example, the program started but attempts to open one or more network
+interfaces failed.</dd>
+
+<dt>WARN</dt>
+<dd>(Warning) An unusual event happened. Although the program will
+continue working normally, the event was sufficiently out of the ordinary
+to warrant drawing attention to it. For example, the authoritative
+server loaded a zone that contained no resource records.</dd>
+
+<dt>INFO</dt>
+<dd>(Information) A normal but significant event has occurred that should
+be recorded, e.g. the program has started or is just about to terminate,
+a new zone has been created, etc.</dd>
+
+<dt>DEBUG</dt>
+<dd>Debug messages are output at this severity. Each message also
+has a debug level associated with it, ranging from 0 (the default)
+for high-level messages and level 99 (the maximum) for the the lowest
+level.</dd>
+</dl>
+
+When logging is enabled for a component, it is enabled for a particular
+severity level and all higher severities. So if logging is enabled
+for INFO messages, WARN, ERROR and FATAL messages will also be logged,
+but not DEBUG ones. If enabled for ERROR, only ERROR and FATAL messages
+will be logged.
+
+As noted above, DEBUG messages are also associated with a debug level.
+This allows the developer to control the amount of debugging information
+produced; the higher the debug level, the more information is output.
+For example, if debugging the NSAS (nameserver address store), debug
+levels might be assigned as follows: a level 0 debug message records
+the creation of a new zone, a level 10 logs every timeout when trying
+to get a nameserver address, a level of 50 records every query for an
+address and a level of 70 records every update of the round-trip time.
+
+Like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug messages associated levels 0 to 25 (inclusive)
+will be output. In fact, it is probably easier to visualise the debug
+levels as part of the severity system:
+@code
+FATAL Most severe
+ERROR
+WARN
+INFO
+DEBUG level 0
+DEBUG level 1
+ :
+DEBUG level 99 Least severe
+@endcode
+When a particular debug level is set, it - and all debug levels and
+severities above it - will be logged.
+
+To try to ensure that the information from different modules is roughly
+comparable for the same debug level, a set of standard debug levels has
+been defined for common types of debug output. (These can be found in
+@ref log_dbglevels.h) However, modules are free to set their own debug
+levels or define additional ones.
+
+@subsection logHierarchical Hierarchical Logging System
+
+When a program writes a message to the logging system, it does so using an
+instance of the @ref isc::log::Logger class. As well as performing the
+write of the message, the logger identifies the source of the message:
+different sources can write to different destinations and can log
+different severities of messages. For example, the logger associated
+with the resolver's cache code could write debugging and other messages
+to a file while all other components only write messages relating to
+errors to the syslog file.
+
+The loggers are hierarchical in that each logger is the child of another
+logger. The top of the hierarchy is the root logger; this does not
+have a parent. The reason for this hierarchy is that unless a logger
+explicitly assigns a value to an attribute (such as severity of messages
+it should log), it picks it up the value from the parent. In BIND 10,
+each component (b10-auth, b10-resolver etc.) has a root logger (named
+after the program) and every other logger in the component is a child
+of that. So in the example above, the error/syslog attributes could be
+associated with the b10-resolver logger while the logger associated with
+the cache sets its own values for the debug/file attributes.
+
+More information about the logging hierarchy can be found in the section
+on Logging configuration in the <a
+href="http://bind10.isc.org/docs/bind10-guide.html#logging">BIND 10
+Guide</a>.
+
+@subsection logSeparationUseText Separation of Messages Use from Message Text
+
+Separating the use of the message from the text associated with it -
+in essence, defining message text in an external file - allows for the
+replacement the supplied text of the messages with a local language version.
+It also means that other attributes can be associated with the message,
+for example, an explanation of the meaning of the message and other
+information such as remedial action in the case of errors.
+
+Each message has an identifier such as "LOG_WRITE_ERROR".
+Within the program, this is the symbol passed to the logging system
+which uses the symbol as an index into a dictionary to retrieve the message
+associated with it (e.g. "unable to open %1 for input"), after which it
+substitutes any message parameters (in this example, the name of the file
+where the write operation failed) and logs the result to the destination.
+
+In BIND 10, a the default text for each message is linked into the
+program. Each program is able to read a locally-defined message file
+when it starts, updating the stored definitions with site-specific text.
+When the message is logged, the updated text is output. However, the
+message identifier is always included in the output so that the origin
+of the message can be identified even if the text has been changed.
+
+@note Local message files have not yet been implemented in BIND 10.
+
+
+
+@section logDeveloperUse Using Logging in a BIND 10 Component
+
+The steps in using the logging system in a BIND 10 component (such as
+an executable or library) are:
+
+<ol>
+<li>Create a message file. This defines messages by an identification
+string and and text that explains the message in more detail. Ideally the
+file should have a file type of ".mes".</li>
+
+<li>Run it through the message compiler to produce the files for your
+module. This step should be included in the build process. The message
+compiler is a BIND10 program and is one of the first programs built and
+linked in the build process. As a result, it should be available for
+compiling the message files of all BIND 10 components and libraries.
+
+For C++ development, the message compiler produces two files in the
+default directory, having the same name as the input file but with file
+types of ".h" and ".cc". For Python, the message compiler will produce
+a Python module containing the symbols.</li>
+
+<li>Include the resultant files in your source code to define message symbols,
+and (for C++) compile the code and link it into your program.</li>
+
+<li>Declare loggers in your code and use them to log messages.</li>
+
+<li>Call the logger initialization function in the program's main module.</li>
+
+</ol>
+
+The following sections describe these steps in more detail.
+
+
+@subsection logMessageFiles Create a Message File
+
+A message file contains message definitions. Typically there
+will be one message file for each component that uses BIND 10 logging.
+An example file could be:
+
+@code
+# Example message file
+
+$NAMESPACE isc::log
+
+% LOG_UNRECOGNISED_DIRECTIVE line %1: unrecognised directive '%2'
+A line starting with a dollar symbol was found, but the first word on the line
+(shown in the message) was not a recognised message compiler directive.
+
+% LOG_WRITE_ERROR error writing to %1: %2
+The specified error was encountered by the message compiler when writing to
+the named output file.
+@endcode
+
+Points to note are:
+
+<ul>
+<li>Leading and trailing spaces are trimmed from each line before it
+is processed. Although the above example has every line starting at
+column 1, the lines could be indented if desired.</li>
+
+<li>Lines starting with "#" are comments are are ignored. Comments must
+be on a line by themselves; inline comments will be interpreted as part
+of the text of that line.</li>
+
+<li>Lines starting with "$" are directives. At present, just one
+directive is recognised:
+ <dl>
+ <dt>$NAMESPACE &lt;namespace-name&gt;</dt>
+ <dd>The sole argument is the name of the namespace in which the
+ symbols are created. In the absence of a $NAMESPACE directive,
+ symbols will be put in the anonymous namespace.</dd>
+ </dl>
+</li>
+
+<li>Lines starting with "%" are message definitions and comprise the message
+identification and the message text. For example:
+@code
+% LOG_WRITE_ERROR error writing to %1: %2
+@endcode
+
+There may be zero or more spaces between the leading "%" and the
+message identification (which, in the example above, is the string
+"LOG_WRITE_ERROR").</li>
+
+<li>The message identification can be any string of letters, digits and
+underscores, but must not start with a digit.</li>
+
+<li>The rest of the line - from the first non-space character to the
+last non- space character - is the text of the message. There are no
+restrictions on what characters may be in this text, other than they be
+printable (so both single-quote (') and double-quote (") characters are
+allowed). The message text may include replacement tokens (the strings
+"%1", "%2" etc.). When a message is logged, these are replaced with the
+arguments passed to the logging call: %1 refers to the first argument,
+%2 to the second etc. Within the message text, the placeholders can
+appear in any order and placeholders can be repeated. Otherwise, the
+message is printed unmodified.</li>
+
+<li>Remaining lines indicate an explanation for the preceding message.
+The explanation can comprise multiple paragraphs, the paragraphs being
+separated by blank lines. These lines are intended to be processed by a
+separate program to generate an error messages manual; they are ignored
+by the message compiler.</li>
+
+<li>Except when used to separate paragraphs in the message explanation,
+blank lines are ignored.</li>
+</ul>
+
+Although there are few restriction on what can be in the message
+identifcation and text, there are a number of conventions used by BIND
+10, both in the contents of the message and in the usage. All code
+should adhere to these:
+
+<ul>
+<li>Message identifications should include at least one underscore.
+The component before the first underscore is a string indicating the
+origin of the message, and the remainder describes the condition.
+So in the example above, the LOG indicates that the error originated
+from the logging library and the "WRITE_ERROR" indicates that there was
+a problem in a write operation.</li>
+
+<li>The part of the message identification describing the error (e.g.
+"WRITE_ERROR" in the example above) should comprise no more than
+two or three words separated by underscores. An excessive number
+of words or overly long message identification should be avoided;
+such information should be put in the text of the message. For example,
+"RESOLVER_ERROR_FETCHING_NAME_FROM_UPSTREAM_SERVER" is excessively long,
+"RESOLVER_FETCH_ERROR" being better.</li>
+
+<li>Similarly, the text of the message should be reasonably concise. It should
+include enough information (possibly supplied at run-time in the form of
+parameters) to allow further investigations to be undertaken if required.
+
+Taking the above example, a suitable error message to indicate that the
+resolver has failed to read a name from an upstream authoritative server
+could be:
+
+@code
+% RESOLVER_FETCH_ERROR fetch from %1 failed, error code %2 (%3)
+@endcode
+
+... where %1 indicates the name or IP address of the server to which the
+fetch was sent, %2 the errno value returned and %3 the message associated
+with that error number (retrieved via a call to "strerror()").
+
+</li>
+
+<li>The message should not have a comma after the message identification.
+The message text should neither start with a capital letter (unless
+the first word is a proper noun or is normally written in capitals)
+nor end with a period. The message reporting system takes care of such
+punctuation.</li>
+
+<li>The parameters substituted into the message text should not include
+line breaks. Messages are normally output to the syslog file which
+has the inbuilt assumption of one line per message. Splitting a message
+across multiple lines makes it awkward to search the file for messages
+and associated information.</li>
+
+<li>The message identifier should be unique across the entire BIND 10
+system. (An error will be reported at system start-up if an identifier
+is repeated.)</li>
+
+<li>A particular message identifier should only be used at one place in
+the BIND 10 code. In this way, if the message indicates a problem, the
+code in question can be quickly identified.</li>
+
+<li>The explanation of the message - the free-form text following the
+message identification - appears in the BIND 10 message manual. It
+should:
+
+<ul>
+<li>Describe the serverity of the message (debug, informational etc.)</li>
+
+<li>Expand on the text of the message. In some cases, such as
+debug messages, the message text may provide more or less sufficient
+description. For warnings and errors, the explanation should provide
+sufficient background to the problem to allow a non-developer to
+understand the issue and to begin fault-finding. If possible, the
+explanation should also include suggested remedial action.</li>
+</ul>
+</ul>
+
+@subsection logSourceFiles Produce Source Files
+The message file created in the previous step is then run through the
+message compiler to produce source files that are included in the BIND
+10 programs.
+
+@subsubsection logMessageCompiler Message Compiler
+The message compiler is a program built in the src/log/compiler directory.
+It is invoked by the command:
+@code
+message [-h] [-v] [-p] [-d dir] <message-file>
+@endcode
+"-v" prints the version number and exits; "-h" prints brief help text.
+The compiler produces source files for C++ unless the "-p" switch is
+specified, in which case it produces Python code. Finally, the "-d"
+switch directs the compiler to produce the output files in the specified
+directory (the default being the current working directory).
+
+<b>C++ Files</b><br/>
+Without the "-p" option, the message compiler processes the message file
+to produce two files:
+
+<ol>
+<li>A C++ header file (called <message-file-name>.h) holding lines of
+the form:
+@code
+namespace <namespace-name> {
+ extern const isc::log::MessageID LOG_BAD_DESTINATION;
+ extern const isc::log::MessageID LOG_BAD_SEVERITY;
+ :
+}
+@endcode
+The symbols define keys in the global message dictionary, with
+the namespace enclosing the symbols set by the $NAMESPACE directive.
+(This is the reason for the restriction on message identifiers - they
+have to be valid C++ symbol names.)</li>
+
+<li>A C++ source file (called <message-file-name>.cc) that holds the definitions
+of the global symbols and code to insert the symbols and messages into
+an internal dictionary.
+
+Symbols are defined to be equal to strings equal to the identifier, e.g.
+@code
+extern const isc::log::MessageID LOG_BAD_DESTINATION = "LOG_BAD_DESTINATION";
+extern const isc::log::MessageID LOG_BAD_SEVERITY = "LOG_BAD_SEVERITY";
+ :
+@endcode
+(The current implementation allows symbols to be compared. However,
+use of strings should not be assumed - a future implementation may change
+this.) In addition, the file declares an array of identifiers/messages
+and an object to add them to the global dictionary, e.g.:
+@code
+namespace {
+ const char* values[] = {
+ "LOG_BAD_DESTINATION", "unrecognized log destination: %1",
+ "LOG_BAD_SEVERITY", "unrecognized log severity: %1",
+ :
+ NULL
+ };
+ const isc::log::MessageInitializer initializer(values);
+}
+@endcode
+
+The constructor of the @ref isc::log::MessageInitializer object retrieves
+the singleton global @ref isc::log::MessageDictionary object (created
+using standard methods to avoid the "static initialization fiasco") and
+adds each identifier and associated text to it. These constructors are run
+when the program starts; a check is made as each identifier is added and,
+if the identifier already exists in the dictionary, a warning message
+is printed to the main logging output when logging is finally enabled.
+The appearance of such a message indicates a programming error.
+</li>
+</ol>
+
+<b>Python Files</b><br/>
+If the "-p" option is given, the compiler produces a Python module defining
+the messages. The content of this is of the form:
+@code
+import isc.log
+ :
+LOG_WRITE_ERROR = isc.log.create_message("LOG_WRITE_ERROR",
+ "error writing to %1 : %2")
+@endcode
+(The definition is output on one line - it is split across two lines in this
+document for readability.)
+
+The module can be imported into other Python code, and messages logged
+in a similar way to C++ using the Python logging library.
+
+@subsubsection logMakefile Include Message Compilation in Makefile
+The source file for the messages is the ".mes" file, but the files used
+by the code (which, in the case of C++, must be compiled and linked)
+are the output of the message compiler. (The compiler is produced very
+early on in the BIND 10 build sequence, so is available for use in the
+building of subsequent components.) To allow this, certain dependencies
+must be included in the Makefile.am for each component that uses logging.
+
+<b>Including Message files in C++ Component Builds</b><br/>
+The following segment from the "hooks" Makefile.am illustrates
+the entries needed.
+@code
+# Define rule to build logging source files from message file
+hooks_messages.h hooks_messages.cc: s-messages
+
+s-messages: hooks_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+ touch $@
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = hooks_messages.h hooks_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda hooks_messages.h hooks_messages.cc s-messages
+@endcode
+The first two rules relate the output .h and .cc files produced by the
+message compiler to the input .mes file. The intermediate "s-messages"
+file is used to overcome synchronization issues with parallel builds
+(where "make" uses multiple processes running in parallel). Note that the
+reference to both the compiler and the input message file are via absolute
+paths defined in terms of Automake macros. In particular it is important
+that the message compiler - which is created during the build process - is
+referred to via the "top_builddir" macro, whereas the input message file -
+which is in the repository - is accessed through the "top_srcdir" macro.
+
+The BUILT_SOURCES line notifies the Automake that the .h and .cc files
+need to be created before they can be used in the compilation, so
+instructs it to organse things so that the message compiler is run first.
+
+As the .mes file is not directly included in any compilation, it will
+not be automatically copied into a distribution created through this
+Makefile.am. The EXTRA_DIST line informs Automake that this file does
+need to be included.
+
+Finally, the intermediate files - the .cc and .h file, as well as the
+intermediate s-messages file - need to be removed when "make clean" is run.
+These files are therefore included in the definition of the CLEANFILES macro.
+
+Not shown are the Makefile.am lines where the .h and .cc file are used. These
+are the same as other lines specifying .h and .cc source files.
+
+<b>Including Message files in Python Component Builds</b><br/>
+The following (modified) segments from the "xfrin" Makefile.am illustrates
+the entries needed.
+
+@code
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.pyc
+ :
+EXTRA_DIST += xfrin_messages.mes
+ :
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.py : xfrin_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/xfrin_messages.mes
+@endcode
+The CLEANFILES lines remove the created Python (and compiled Python)
+code when "make clean" is run.
+
+The EXTRA_DIST line ensures that the .mes file is copied to the
+distribution tarball.
+
+The final dependency shows the use of the message compiler to
+create the Python message module in a working directory.
+
+
+@subsection logUsage Using Logging Files in Program Development
+
+@subsubsection logCppUsage Use in a C++ Program or Module
+To use logging in a C++ program or module:
+
+<ol>
+<li>Build the message header file and source file as described above.</li>
+
+<li>In each C++ file in which logging is to be used, declare a logger
+through which the message will be logged.
+
+@code
+isc::log::Logger logger("name");
+@endcode
+This declaration can be per-function, or it can be declared statically
+in file scope. The string passed to the constructor is the name of
+the logger (it can be any string) and is used when configuring it.
+(Remember though that the name of root logger for the program will be
+prepended to the name chosen. So if, for example, the name "cache"
+is chosen and the model is included in the "b10-resolver" program, the
+full name of the logger will be "b10-resolver.cache".) Loggers with
+the same name share the same configuration. For this reason if, as is
+usual, messages logged in different files in the same component (e.g.
+hooks module, nameserver address store, etc.) originate from loggers
+with the same name, the logger declaration can be placed into a header
+file.</li>
+
+<li>Issue logging calls using supplied macros in "log/macros.h", e.g.
+@code
+LOG_ERROR(logger, LOG_WRITE_ERROR).arg("output.txt");
+LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUP_CANCEL).arg(zone);
+@endcode
+All macros (with the exception of LOG_DEBUG) take two arguments:
+the C++ logger object that will be used to log the message, and the
+identification of the message to be logged. LOG_DEBUG takes three
+arguments, the additional one being the debug level associated with
+the message. The .arg() call appended to the end of the LOG_XXX()
+macro handles the arguments to the message. A chain of these is used
+in cases where a message takes multiple arguments, e.g.
+@code
+LOG_DEBUG(nsas_logger, NSAS_DBG_RTT, NSAS_UPDATE_RTT)
+ .arg(addresses_[family][index].getAddress().toText())
+ .arg(old_rtt).arg(new_rtt);
+@endcode
+Using the macros is more efficient than direct calls to the methods on
+the logger class: they avoid the overhead of evaluating the parameters
+to arg() if the logging settings are such that the message is not going
+to be output (e.g. it is a DEBUG message and the logging is set to output
+messages of INFO severity or above).</li>
+
+<li>The main program unit must include a call to isc::log::initLogger()
+(described in more detail below) to set the initial logging severity, debug log
+level, and external message file.
+</ol>
+
+@subsubsection logPythonUsage Use in a Python Module
+To use logging in a Python module:
+<ol>
+<li>Build message module as described above.</li>
+
+<li>Declare a logger through which the message will be logged.
+@code
+isc.log.Logger logger("name")
+@endcode
+The string passed to the constructor is the name of the logger (it can
+be any string) and is used when configuring it. Loggers with the same
+name share the same configuration.</li>
+
+<li>Issue calls to the logging methods:
+@code
+logger.error(LOG_WRITE_ERROR, "output.txt")
+@endcode
+The message parameters are included as trailing arguments in the
+logger call.</li>
+
+<li>The main program unit must include a call to isc.log.init() (described
+in more detail below) to set the to set the logging severity, debug log
+level, and external message file. The settings remain in effect until
+the logging configuration is read, so are the ones used during program
+initialization.</li>
+</ol>
+
+
+@subsection logInitialization Logging Initialization
+In all cases, if an attempt is made to use a logging method before
+the logging has been initialized, the program will terminate with a
+LoggingNotInitialized exception.
+
+@subsection logInitializationCpp C++ Initialization
+Logging Initialization is carried out by calling @ref
+isc::log::initLogger(). There are two variants to the call, one for
+use by production programs and one for use by unit tests.
+
+@subsubsection logInitializationCppVariant1 Variant #1, Used by Production Programs
+The call that should be used by all production programs is:
+@code
+void isc::log::initLogger(const std::string& root,
+ isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
+@endcode
+Arguments are:
+<dl>
+<dt><code>root</code></dt>
+<dd>Name of the root logger. This should be the name of the program
+(e.g. "b10-auth") and is used when configuring logging.</dd>
+
+<dt><code>severity</code></dt>
+<dd>Default severity that the program will start logging with. Although
+this may be overridden when the program obtains its configuration from
+the configuration database, this is the severity that it used until then.
+The logging severity is one of the enum defined in @ref logger.h, i.e.
+
+@code
+isc::log::DEBUG
+isc::log::INFO
+isc::log::WARN
+isc::log::ERROR
+isc::log::FATAL
+isc::log::NONE
+@endcode
+
+(The level NONE may be used to disable logging.)
+</dd>
+
+<dt><code>dbglevel</code></dt>
+<dd>The debug log level is only interpreted when the severity is
+isc::log::DEBUG and is an integer ranging from 0 to 99. 0 should be
+used for the highest-level debug messages and 99 for the lowest-level
+(and typically more verbose) messages.</dd>
+
+<dt><code>file</code></dt>
+<dd>The name of a local message file. This will be read and its
+definitions used to replace the compiled-in text of the messages.
+The default value of NULL indicates that no local message file is
+supplied.</dd>
+
+<dt><code>buffer</code></dt>
+<dd>If set to true, initial log messages will be internally buffered,
+until the first time a logger specification is processed. This
+way the program can use logging before even processing its logging
+configuration. As soon as any specification is processed (even an
+empty one), the buffered log messages will be flushed according to
+the specification. Note that if this option is used, the program
+SHOULD call one of the @ref isc::log::LoggerManager::process() calls.
+(If you are using the built-in logging configuration handling in @ref
+isc::config::ModuleCCSession, this is automatically handled.) If the
+program exits before this is done, all log messages are dumped in a raw
+format to stdout (so that no messages get lost).</dd>
+</dl>
+
+@subsubsection logInitializationCppVariant2 Variant #2, Used by Unit Tests
+@code
+void isc::log::initLogger()
+@endcode
+This is the call that should be used by unit tests. In this variant,
+all the options are supplied by environment variables: it should not
+be used for production programs to avoid the chance that the program
+operation is affected by inadvertently-defined environment variables. The
+environment variables are:
+
+<dl>
+<dt>B10_LOGGER_ROOT</dt>
+<dd>Sets the "root" for the unit test. If not defined, the name "bind10"
+is used.</dd>
+
+<dt>B10_LOGGER_SEVERITY</dt>
+<dd>The severity to set for the root logger in the unit test.
+Valid values are "DEBUG", "INFO", "WARN", "ERROR", "FATAL" and "NONE".
+If not defined, "INFO" is used.</dd>
+
+<dt>B10_LOGGER_DBGLEVEL</dt>
+<dd>If B10_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can
+be a number between 0 and 99, and defaults to 0.</dd>
+
+<dt>B10_LOGGER_LOCALMSG</dt>
+<dd>If defined, points to a local message file. The default is not to
+use a local message file.</dd>
+
+<dt>B10_LOGGER_DESTINATION</dt>
+<dd>The location to which log message are written. This can be one of:
+<ul>
+<li><b>stdout</b> Message are written to stdout.</li>
+<li><b>stderr</b> Messages are written to stderr.</li>
+<li><b>syslog[:facility]</b> Messages are written to syslog. If the
+optional "facility" is used, the messages are written using that facility.
+(This defaults to "local0" if not specified.)</li>
+<li><b>Anything else</b> Interpreted as the name of a file to which
+output is appended. If the file does not exist, a new one is opened.</li>
+</ul>
+In the case of "stdout", "stderr" and "syslog", they must be written exactly
+as is - no leading or trailing spaces, and in lower-case.</dd>
+</dl>
+
+@subsection logInitializationPython Python Initialization
+To initialize the logger in a Python program, the "init" method must be
+called:
+@code
+isc.log.init(name, severity, debuglevel, file, buffer)
+@endcode
+
+<dl>
+<dt><code>name</code></dt>
+<dd>String giving the name of the root logger. This is the only mandatory
+argument, the rest are optional.</dd>
+
+<dt><code>severity</code></dt>
+<dd>The severity, and is one of the strings "DEBUG", INFO" etc.
+The default is "INFO".</dd>
+
+<dt><code>debuglevel</code></dt>
+<dd>Debug level, an integer between 0 and 99. A default value of 0 will
+be used if this is not specified.</dd>
+
+<dt><code>file</code></dt>
+<dd>Name of the external message file (if present). By default, no
+external message file is used.</dd>
+
+<dt><code>buffer</code></dt>
+<dd>If set to true, initial log messages will be internally buffered,
+until the first time a logger specification is processed. This
+way the program can use logging before even processing its logging
+configuration. By default, no buffer is used.</dd>
+</dl>
+
+
+@section logNotes Notes on the Use of Logging
+One thing that should always be kept in mind is whether the logging
+could be used as a means for a DOS attack. For example, if a warning
+message is logged every time an invalid packet is received, an attacker
+could simply send large numbers of invalid packets. Of course, warnings
+could be disabled (or just warnings for that that particular logger),
+but nevertheless the message is an attack vector. As a general rule,
+if the message can be triggered by a user action, it can be used as an
+attack vector.
+
+There are two approaches to get round this:
+<ol>
+<li>Log messages generated by such user actions as DEBUG messages. DEBUG
+is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically enabled. Choosing a suitable debug level for
+such messages will select only those messages and not the more general
+debug messages.</li>
+
+<li>Record system-related and packet-related messages via different
+loggers. As the loggers are independent and the severity levels
+independent, fine-tuning of what and what is not recorded can be achieved.</li>
+
+</ol>
+*/
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
index 3bfc56cd82..577042bc6b 100644
--- a/src/lib/log/message_dictionary.cc
+++ b/src/lib/log/message_dictionary.cc
@@ -63,13 +63,13 @@ MessageDictionary::load(const char* messages[]) {
vector<std::string> duplicates;
int i = 0;
while (messages[i]) {
-
- // ID present, so note it and point to text.
- const MessageID ident(messages[i++]);
+ // ID present, so point to text.
+ ++i;
if (messages[i]) {
-
+ const MessageID ident(messages[i - 1]);
// Text not null, note it and point to next ident.
- string text(messages[i++]);
+ const std::string text(messages[i]);
+ ++i;
// Add ID and text to message dictionary, noting if the ID was
// already present.
diff --git a/src/lib/log/tests/.gitignore b/src/lib/log/tests/.gitignore
index e7e12d6e2f..05d0a0375d 100644
--- a/src/lib/log/tests/.gitignore
+++ b/src/lib/log/tests/.gitignore
@@ -15,3 +15,4 @@
/run_unittests
/severity_test.sh
/tempdir.h
+/s-messages
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
index daadb7c4ff..729fdacabf 100644
--- a/src/lib/log/tests/logger_example.cc
+++ b/src/lib/log/tests/logger_example.cc
@@ -215,7 +215,7 @@ int main(int argc, char** argv) {
return (1);
}
try {
- cur_opt.maxsize = boost::lexical_cast<unsigned int>(optarg);
+ cur_opt.maxver = boost::lexical_cast<unsigned int>(optarg);
} catch (boost::bad_lexical_cast&) {
std::cerr << "Maximum version (-m) argument must be a positive "
"integer\n";
diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc
index e615c41df7..90f8904532 100644
--- a/src/lib/log/tests/logger_manager_unittest.cc
+++ b/src/lib/log/tests/logger_manager_unittest.cc
@@ -36,6 +36,9 @@
#include "tempdir.h"
+#include <sys/types.h>
+#include <regex.h>
+
using namespace isc;
using namespace isc::log;
using namespace std;
@@ -323,3 +326,81 @@ TEST_F(LoggerManagerTest, FileSizeRollover) {
(void) unlink(prev_name[i].c_str());
}
}
+
+namespace { // begin unnamed namespace
+
+// When we begin to use C++11, we could replace use of POSIX API with
+// <regex>.
+
+class RegexHolder {
+public:
+ RegexHolder(const char* expr, const int flags = REG_EXTENDED) {
+ const int rc = regcomp(&regex_, expr, flags);
+ if (rc) {
+ regfree(&regex_);
+ throw;
+ }
+ }
+
+ ~RegexHolder() {
+ regfree(&regex_);
+ }
+
+ regex_t* operator*() {
+ return (&regex_);
+ }
+
+private:
+ regex_t regex_;
+};
+
+} // end of unnamed namespace
+
+// Check that the logger correctly outputs the full formatted layout
+// pattern.
+TEST_F(LoggerManagerTest, checkLayoutPattern) {
+ // Create a specification for the file logger and use the manager to
+ // connect the "filelogger" logger to it.
+ SpecificationForFileLogger file_spec;
+
+ // For the first test, we want to check that the file is created
+ // if it does not already exist. So delete the temporary file before
+ // logging the first message.
+ unlink(file_spec.getFileName().c_str());
+
+ // Set up the file appenders.
+ LoggerManager manager;
+ manager.process(file_spec.getSpecification());
+
+ // Try logging to the file. Local scope is set to ensure that the logger
+ // is destroyed before we reset the global logging.
+ {
+ Logger logger(file_spec.getLoggerName().c_str());
+ LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
+ }
+
+ LoggerManager::reset();
+
+ // Access the file for input
+ const std::string& filename = file_spec.getFileName();
+ ifstream infile(filename.c_str());
+ if (! infile.good()) {
+ FAIL() << "Unable to open the logging file " << filename;
+ }
+
+ std::string line;
+ std::getline(infile, line);
+
+ RegexHolder regex(// %D{%Y-%m-%d %H:%M:%S.%q}
+ "^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}[[:space:]]"
+ "[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]+[[:space:]]"
+ // %-5p
+ "[[:alpha:]]{1,5}[[:space:]]"
+ // [%c/%i]
+ "\\[[[:alnum:]\\-\\.]+/[[:digit:]]+\\][[:space:]]"
+ );
+
+ const int re = regexec(*regex, line.c_str(), 0, NULL, 0);
+ ASSERT_EQ(0, re)
+ << "Logged message does not match expected layout pattern";
+}
diff --git a/src/lib/nsas/.gitignore b/src/lib/nsas/.gitignore
index 109ef0433b..691e0107e5 100644
--- a/src/lib/nsas/.gitignore
+++ b/src/lib/nsas/.gitignore
@@ -1,2 +1,3 @@
/nsas_messages.cc
/nsas_messages.h
+/s-messages
diff --git a/src/lib/python/isc/cc/tests/session_test.py b/src/lib/python/isc/cc/tests/session_test.py
index 65afb6ca74..7b4771c92c 100644
--- a/src/lib/python/isc/cc/tests/session_test.py
+++ b/src/lib/python/isc/cc/tests/session_test.py
@@ -334,10 +334,10 @@ class testSession(unittest.TestCase):
self.assertEqual(sess._sequence, 1)
self.assertEqual(sess._next_sequence(), 2)
self.assertEqual(sess._sequence, 2)
- sess._sequence = 47805
- self.assertEqual(sess._sequence, 47805)
- self.assertEqual(sess._next_sequence(), 47806)
- self.assertEqual(sess._sequence, 47806)
+ sess._sequence = 56175
+ self.assertEqual(sess._sequence, 56175)
+ self.assertEqual(sess._next_sequence(), 56176)
+ self.assertEqual(sess._sequence, 56176)
def test_group_subscribe(self):
sess = MySession()
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index e2b135799a..3683f339e8 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -53,6 +53,22 @@ def spec_part_is_any(spec_part):
return (type(spec_part) == dict and 'item_type' in spec_part and
spec_part['item_type'] == "any")
+def _type_as_string(value):
+ if type(value) == int:
+ return 'integer'
+ elif type(value) == float:
+ return 'real'
+ elif type(value) == bool:
+ return 'boolean'
+ elif type(value) == str:
+ return 'string'
+ elif type(value) == list:
+ return 'list'
+ elif type(value) == dict:
+ return 'map'
+ else:
+ return '<unknown>'
+
def check_type(spec_part, value):
"""Does nothing if the value is of the correct type given the
specification part relevant for the value. Raises an
@@ -65,27 +81,35 @@ def check_type(spec_part, value):
if data_type == "integer":
if type(value) != int:
- raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
+ raise isc.cc.data.DataTypeError('%s is not an integer (%s was passed)' % \
+ (str(value), _type_as_string(value)))
if value > sys.maxsize:
- raise isc.cc.data.DataTypeError(str(value) + " is too large an integer")
+ raise isc.cc.data.DataTypeError('%s is too large an integer' % \
+ (str(value)))
elif data_type == "real":
if type(value) != float:
- raise isc.cc.data.DataTypeError(str(value) + " is not a real")
+ raise isc.cc.data.DataTypeError('%s is not a real (%s was passed)' % \
+ (str(value), _type_as_string(value)))
if float(value) > sys.float_info.max:
- raise isc.cc.data.DataTypeError(str(value) + " is too large a float")
+ raise isc.cc.data.DataTypeError('%s is too large for a float' % \
+ (str(value)))
elif data_type == "boolean" and type(value) != bool:
- raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
+ raise isc.cc.data.DataTypeError('%s is not a boolean (%s was passed)' % \
+ (str(value), _type_as_string(value)))
elif data_type == "string" and type(value) != str:
- raise isc.cc.data.DataTypeError(str(value) + " is not a string")
+ raise isc.cc.data.DataTypeError('%s is not a string (%s was passed)' % \
+ (str(value), _type_as_string(value)))
elif data_type == "list":
if type(value) != list:
- raise isc.cc.data.DataTypeError(str(value) + " is not a list")
+ raise isc.cc.data.DataTypeError('%s is not a list (%s was passed)' % \
+ (str(value), _type_as_string(value)))
else:
for element in value:
check_type(spec_part['list_item_spec'], element)
elif data_type == "map" and type(value) != dict:
# todo: check types of map contents too
- raise isc.cc.data.DataTypeError(str(value) + " is not a map")
+ raise isc.cc.data.DataTypeError('%s is not a map (%s was passed)' % \
+ (str(value), _type_as_string(value)))
def convert_type(spec_part, value):
"""Convert the given value(type is string) according specification
diff --git a/src/lib/python/isc/config/tests/config_data_test.py b/src/lib/python/isc/config/tests/config_data_test.py
index 83f22c925c..74d13f27ca 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -667,7 +667,7 @@ class TestMultiConfigData(unittest.TestCase):
'modified': False,
'name': 'Spec22/value9/v92/v92b',
'type': 'integer',
- 'value': 47806
+ 'value': 56176
}
]
maps = self.mcd.get_value_maps("/Spec22/value9")
diff --git a/src/lib/resolve/.gitignore b/src/lib/resolve/.gitignore
index 292ed1a794..87dc694968 100644
--- a/src/lib/resolve/.gitignore
+++ b/src/lib/resolve/.gitignore
@@ -1,2 +1,3 @@
/resolve_messages.cc
/resolve_messages.h
+/s-messages
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index 623d731b8e..dddfab3e6b 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -378,7 +378,6 @@ protected:
DNSLookup* lookup = NULL,
DNSAnswer* answer = NULL) :
io_(io_service),
- done_(false),
message_(new Message(Message::PARSE)),
answer_message_(new Message(Message::RENDER)),
respbuf_(new OutputBuffer(0)),
@@ -407,7 +406,6 @@ protected:
protected:
IOService& io_;
- bool done_;
private:
// Currently unused; these will be used for testing
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
index d6019d09db..8c2e9e3ebf 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_2.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -462,7 +462,8 @@ public:
tcp_cumulative_ += length;
bool complete = false;
if (tcp_cumulative_ > 2) {
- uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ uint16_t dns_length = readUint16(tcp_receive_buffer_,
+ sizeof(tcp_receive_buffer_));
complete = ((dns_length + 2) == tcp_cumulative_);
}
diff --git a/src/lib/resolve/tests/recursive_query_unittest_3.cc b/src/lib/resolve/tests/recursive_query_unittest_3.cc
index 7803b88870..acf60d6093 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_3.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_3.cc
@@ -336,7 +336,8 @@ public:
tcp_cumulative_ += length;
bool complete = false;
if (tcp_cumulative_ > 2) {
- uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ uint16_t dns_length = readUint16(tcp_receive_buffer_,
+ sizeof(tcp_receive_buffer_));
complete = ((dns_length + 2) == tcp_cumulative_);
}
diff --git a/src/lib/server_common/.gitignore b/src/lib/server_common/.gitignore
index e25a98f90e..1c16f77db0 100644
--- a/src/lib/server_common/.gitignore
+++ b/src/lib/server_common/.gitignore
@@ -1,2 +1,3 @@
/server_common_messages.cc
/server_common_messages.h
+/s-messages
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index 7b0b1bbeb4..8b1337116b 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -72,12 +72,7 @@ SrvTestBase::createRequestPacket(Message& message,
const char* const remote_address,
uint16_t remote_port)
{
- if (context == NULL) {
- message.toWire(request_renderer);
- } else {
- message.toWire(request_renderer, *context);
- }
-
+ message.toWire(request_renderer, context);
endpoint.reset(IOEndpoint::create(protocol, IOAddress(remote_address),
remote_port));
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
diff --git a/src/lib/util/io_utilities.h b/src/lib/util/io_utilities.h
index cd35364f7c..4cfa999f75 100644
--- a/src/lib/util/io_utilities.h
+++ b/src/lib/util/io_utilities.h
@@ -15,6 +15,7 @@
#ifndef IO_UTILITIES_H
#define IO_UTILITIES_H
+#include <exceptions/exceptions.h>
#include <cstddef>
namespace isc {
@@ -28,10 +29,17 @@ namespace util {
/// \param buffer Data buffer at least two bytes long of which the first two
/// bytes are assumed to represent a 16-bit integer in network-byte
/// order.
+/// \param length Length of the data buffer.
///
/// \return Value of 16-bit integer
inline uint16_t
-readUint16(const void* buffer) {
+readUint16(const void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint16_t");
+ }
+
const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
uint16_t result = (static_cast<uint16_t>(byte_buffer[0])) << 8;
@@ -48,10 +56,17 @@ readUint16(const void* buffer) {
/// \param value 16-bit value to convert
/// \param buffer Data buffer at least two bytes long into which the 16-bit
/// value is written in network-byte order.
+/// \param length Length of the data buffer.
///
/// \return pointer to the next byte after stored value
inline uint8_t*
-writeUint16(uint16_t value, void* buffer) {
+writeUint16(uint16_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint16_t");
+ }
+
uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
byte_buffer[0] = static_cast<uint8_t>((value & 0xff00U) >> 8);
@@ -65,10 +80,17 @@ writeUint16(uint16_t value, void* buffer) {
/// \param buffer Data buffer at least four bytes long of which the first four
/// bytes are assumed to represent a 32-bit integer in network-byte
/// order.
+/// \param length Length of the data buffer.
///
/// \return Value of 32-bit unsigned integer
inline uint32_t
-readUint32(const uint8_t* buffer) {
+readUint32(const uint8_t* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint32_t");
+ }
+
const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
uint32_t result = (static_cast<uint32_t>(byte_buffer[0])) << 24;
@@ -84,10 +106,17 @@ readUint32(const uint8_t* buffer) {
/// \param value 32-bit value to convert
/// \param buffer Data buffer at least four bytes long into which the 32-bit
/// value is written in network-byte order.
+/// \param length Length of the data buffer.
///
/// \return pointer to the next byte after stored value
inline uint8_t*
-writeUint32(uint32_t value, uint8_t* buffer) {
+writeUint32(uint32_t value, uint8_t* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint32_t");
+ }
+
uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
byte_buffer[0] = static_cast<uint8_t>((value & 0xff000000U) >> 24);
diff --git a/src/lib/util/tests/io_utilities_unittest.cc b/src/lib/util/tests/io_utilities_unittest.cc
index 8e0c232d26..d7d3b0eb76 100644
--- a/src/lib/util/tests/io_utilities_unittest.cc
+++ b/src/lib/util/tests/io_utilities_unittest.cc
@@ -42,11 +42,15 @@ TEST(asioutil, readUint16) {
data[0] = i8;
data[1] = j8;
buffer.setPosition(0);
- EXPECT_EQ(buffer.readUint16(), readUint16(data));
+ EXPECT_EQ(buffer.readUint16(), readUint16(data, sizeof(data)));
}
}
}
+TEST(asioutil, readUint16OutOfRange) {
+ uint8_t data;
+ EXPECT_THROW(readUint16(&data, sizeof(data)), isc::OutOfRange);
+}
TEST(asioutil, writeUint16) {
@@ -64,7 +68,7 @@ TEST(asioutil, writeUint16) {
buffer.writeUint16(i16);
// ... and the test data
- writeUint16(i16, test);
+ writeUint16(i16, test, sizeof(test));
// ... and compare
const uint8_t* ref = static_cast<const uint8_t*>(buffer.getData());
@@ -73,6 +77,12 @@ TEST(asioutil, writeUint16) {
}
}
+TEST(asioutil, writeUint16OutOfRange) {
+ uint16_t i16 = 42;
+ uint8_t data;
+ EXPECT_THROW(writeUint16(i16, &data, sizeof(data)), isc::OutOfRange);
+}
+
// test data shared amount readUint32 and writeUint32 tests
const static uint32_t test32[] = {
0,
@@ -93,11 +103,15 @@ TEST(asioutil, readUint32) {
uint32_t tmp = htonl(test32[i]);
memcpy(&data[offset], &tmp, sizeof(uint32_t));
- EXPECT_EQ(test32[i], readUint32(&data[offset]));
+ EXPECT_EQ(test32[i], readUint32(&data[offset], sizeof(uint32_t)));
}
}
}
+TEST(asioutil, readUint32OutOfRange) {
+ uint8_t data[3];
+ EXPECT_THROW(readUint32(data, sizeof(data)), isc::OutOfRange);
+}
TEST(asioutil, writeUint32) {
uint8_t data[8];
@@ -107,7 +121,8 @@ TEST(asioutil, writeUint32) {
// it 4 times.
for (int offset=0; offset < 4; offset++) {
for (int i=0; i < sizeof(test32)/sizeof(uint32_t); i++) {
- uint8_t* ptr = writeUint32(test32[i], &data[offset]);
+ uint8_t* ptr = writeUint32(test32[i], &data[offset],
+ sizeof(uint32_t));
EXPECT_EQ(&data[offset]+sizeof(uint32_t), ptr);
@@ -117,3 +132,9 @@ TEST(asioutil, writeUint32) {
}
}
}
+
+TEST(asioutil, writeUint32OutOfRange) {
+ uint32_t i32 = 28;
+ uint8_t data[3];
+ EXPECT_THROW(writeUint32(i32, data, sizeof(data)), isc::OutOfRange);
+}
diff --git a/tests/lettuce/README b/tests/lettuce/README
index 48c4043464..786fe0220a 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -21,9 +21,9 @@ it is called python-pip. On FreeBSD the port is devel/py-pip.
Running the tests
-----------------
-At this moment, we have a fixed port for local tests in our setups, port 47806.
+At this moment, we have a fixed port for local tests in our setups, port 56176.
This port must be free. (TODO: can we make this run-time discovered?).
-Port 47805 is used for cmdctl, and must also be available.
+Port 56175 is used for cmdctl, and must also be available.
(note, we will need to extend this to a range, or if possible, we will need to
do some on-the-fly available port finding)
diff --git a/tests/lettuce/configurations/.gitignore b/tests/lettuce/configurations/.gitignore
index 69d136fae7..db54954fb3 100644
--- a/tests/lettuce/configurations/.gitignore
+++ b/tests/lettuce/configurations/.gitignore
@@ -1,2 +1,3 @@
/bindctl_commands.config
/example.org.config
+/root.config
diff --git a/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807 b/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807
deleted file mode 100644
index d8f0b5fb91..0000000000
--- a/tests/lettuce/configurations/DO_NOT_USE_127.0.0.1:47807
+++ /dev/null
@@ -1,6 +0,0 @@
-Note well:
-
-In some configuration we intentionally use an IPv6 address (::1) with
-port 47807. DO NOT CHANGE THAT; at least do not change it to
-127.0.0.1:47807. See git e3f4b290d17a68db728166cdffcbe93517966e8b
-for why.
diff --git a/tests/lettuce/configurations/auth/auth_badzone.config.orig b/tests/lettuce/configurations/auth/auth_badzone.config.orig
index f86882aba3..02e2186956 100644
--- a/tests/lettuce/configurations/auth/auth_badzone.config.orig
+++ b/tests/lettuce/configurations/auth/auth_badzone.config.orig
@@ -9,7 +9,7 @@
},
"Auth": {
"listen_on": [{
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
}]
},
diff --git a/tests/lettuce/configurations/auth/auth_basic.config.orig b/tests/lettuce/configurations/auth/auth_basic.config.orig
index 24f615c69a..e94e73a77b 100644
--- a/tests/lettuce/configurations/auth/auth_basic.config.orig
+++ b/tests/lettuce/configurations/auth/auth_basic.config.orig
@@ -9,7 +9,7 @@
},
"Auth": {
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/bindctl/bindctl.config.orig b/tests/lettuce/configurations/bindctl/bindctl.config.orig
index ef0e8e24c8..51615cb547 100644
--- a/tests/lettuce/configurations/bindctl/bindctl.config.orig
+++ b/tests/lettuce/configurations/bindctl/bindctl.config.orig
@@ -10,7 +10,7 @@
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/bindctl_commands.config.orig b/tests/lettuce/configurations/bindctl_commands.config.orig
index 980262b7d8..1b560add21 100644
--- a/tests/lettuce/configurations/bindctl_commands.config.orig
+++ b/tests/lettuce/configurations/bindctl_commands.config.orig
@@ -10,7 +10,7 @@
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/ddns/ddns.config.orig b/tests/lettuce/configurations/ddns/ddns.config.orig
index 02978be37a..c3bf319782 100644
--- a/tests/lettuce/configurations/ddns/ddns.config.orig
+++ b/tests/lettuce/configurations/ddns/ddns.config.orig
@@ -21,7 +21,7 @@
"database_file": "data/ddns/example.org.sqlite3",
"listen_on": [
{
- "port": 47806,
+ "port": 56176,
"address":
"127.0.0.1"
}
diff --git a/tests/lettuce/configurations/ddns/noddns.config.orig b/tests/lettuce/configurations/ddns/noddns.config.orig
index d075924860..2b3f9cabaf 100644
--- a/tests/lettuce/configurations/ddns/noddns.config.orig
+++ b/tests/lettuce/configurations/ddns/noddns.config.orig
@@ -14,7 +14,7 @@
"database_file": "data/ddns/example.org.sqlite3",
"listen_on": [
{
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
}
]
diff --git a/tests/lettuce/configurations/default_user.csv b/tests/lettuce/configurations/default_user.csv
new file mode 100644
index 0000000000..e13e194eed
--- /dev/null
+++ b/tests/lettuce/configurations/default_user.csv
@@ -0,0 +1 @@
+root,bind10
diff --git a/tests/lettuce/configurations/example.org.config.orig b/tests/lettuce/configurations/example.org.config.orig
index 7da6304fba..005096c544 100644
--- a/tests/lettuce/configurations/example.org.config.orig
+++ b/tests/lettuce/configurations/example.org.config.orig
@@ -10,7 +10,7 @@
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
index 2eadedbeb5..a7c4d9ffaf 100644
--- a/tests/lettuce/configurations/example.org.inmem.config
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -9,7 +9,7 @@
},
"Auth": {
"listen_on": [{
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
}]
},
diff --git a/tests/lettuce/configurations/example2.org.config b/tests/lettuce/configurations/example2.org.config
index 3bb3330ca8..0425860760 100644
--- a/tests/lettuce/configurations/example2.org.config
+++ b/tests/lettuce/configurations/example2.org.config
@@ -11,7 +11,7 @@
"Auth": {
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
- "port": 47807,
+ "port": 56177,
"address": "::1"
} ]
},
diff --git a/tests/lettuce/configurations/glue.config b/tests/lettuce/configurations/glue.config
index fc301d3887..13df1a431c 100644
--- a/tests/lettuce/configurations/glue.config
+++ b/tests/lettuce/configurations/glue.config
@@ -9,7 +9,7 @@
},
"Auth": {
"listen_on": [{
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
}]
},
diff --git a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
index d93a8c6964..f49498a376 100644
--- a/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
+++ b/tests/lettuce/configurations/inmemory_over_sqlite3/secondary.conf
@@ -9,7 +9,7 @@
},
"Auth": {
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/ixfr-out/testset1-config.db b/tests/lettuce/configurations/ixfr-out/testset1-config.db
index 38d29a77d7..9623d8e77d 100644
--- a/tests/lettuce/configurations/ixfr-out/testset1-config.db
+++ b/tests/lettuce/configurations/ixfr-out/testset1-config.db
@@ -29,11 +29,11 @@
"database_file": "data/ixfr-out/zones.sqlite3",
"listen_on": [
{
- "port": 47806,
+ "port": 56176,
"address": "::"
},
{
- "port": 47806,
+ "port": 56176,
"address": "0.0.0.0"
}
]
diff --git a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
index 96e25d8113..5d9ca6849a 100644
--- a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
+++ b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
@@ -10,7 +10,7 @@
"Auth": {
"database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/no_db_file.config b/tests/lettuce/configurations/no_db_file.config
index 9e6c168118..3229238d45 100644
--- a/tests/lettuce/configurations/no_db_file.config
+++ b/tests/lettuce/configurations/no_db_file.config
@@ -11,7 +11,7 @@
"Auth": {
"database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/nsec3/nsec3_auth.config b/tests/lettuce/configurations/nsec3/nsec3_auth.config
index 5dfffa1d57..d2a0643132 100644
--- a/tests/lettuce/configurations/nsec3/nsec3_auth.config
+++ b/tests/lettuce/configurations/nsec3/nsec3_auth.config
@@ -11,7 +11,7 @@
"Auth": {
"datasources": [
{"zones": [{"origin": "example.", "file": "configurations/nsec3/rfc5155-example.zone.signed"}], "type": "memory"}],
- "listen_on": [{"port": 47806, "address": "0.0.0.0"}
+ "listen_on": [{"port": 56176, "address": "0.0.0.0"}
]
},
"data_sources": {
diff --git a/tests/lettuce/configurations/resolver/resolver_basic.config.orig b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
index fe5ddd04cf..7f7004fb5f 100644
--- a/tests/lettuce/configurations/resolver/resolver_basic.config.orig
+++ b/tests/lettuce/configurations/resolver/resolver_basic.config.orig
@@ -13,7 +13,7 @@
"from": "127.0.0.1"
} ],
"listen_on": [ {
- "port": 47806,
+ "port": 56176,
"address": "127.0.0.1"
} ]
},
diff --git a/tests/lettuce/configurations/root.config.orig b/tests/lettuce/configurations/root.config.orig
new file mode 100644
index 0000000000..282b7d1075
--- /dev/null
+++ b/tests/lettuce/configurations/root.config.orig
@@ -0,0 +1,40 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/root.sqlite3",
+ "listen_on": [ {
+ "port": 56176,
+ "address": "127.0.0.1"
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [
+ {
+ "type": "sqlite3",
+ "cache-enable": false,
+ "params": {
+ "database_file": "data/root.sqlite3"
+ }
+ }
+ ]
+ }
+ },
+ "Stats": {
+ "poll-interval": 60
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/inmem_slave.conf b/tests/lettuce/configurations/xfrin/inmem_slave.conf
index fedf372e25..4f6ac1788e 100644
--- a/tests/lettuce/configurations/xfrin/inmem_slave.conf
+++ b/tests/lettuce/configurations/xfrin/inmem_slave.conf
@@ -11,7 +11,7 @@
"database_file": "data/inmem-xfrin.sqlite3",
"listen_on": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"data_sources": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index d7ea9a56e2..f3529fccfe 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -11,7 +11,7 @@
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"address": "::1",
- "port": 47807
+ "port": 56177
} ]
},
"data_sources": {
@@ -30,7 +30,7 @@
} ],
"also_notify": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"Init": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
index 34d2d036fd..d25f69cd91 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
@@ -11,7 +11,7 @@
"database_file": "data/xfrin-diffs.sqlite3",
"listen_on": [ {
"address": "::1",
- "port": 47807
+ "port": 56177
} ]
},
"data_sources": {
@@ -30,7 +30,7 @@
} ],
"also_notify": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"Stats": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
index bccadf727d..ecb9e054c5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_nons.conf.orig
@@ -11,7 +11,7 @@
"database_file": "data/example.org-nons.sqlite3",
"listen_on": [ {
"address": "::1",
- "port": 47807
+ "port": 56177
} ]
},
"data_sources": {
@@ -30,7 +30,7 @@
} ],
"also_notify": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"Stats": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
index 755c91b709..5eca05fc22 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
@@ -11,7 +11,7 @@
"database_file": "data/example.org.sqlite3",
"listen_on": [ {
"address": "127.0.0.1",
- "port": 47809
+ "port": 56179
} ]
},
"data_sources": {
@@ -30,7 +30,7 @@
} ],
"also_notify": [ {
"address": "127.0.0.1",
- "port": 47806
+ "port": 56176
} ]
},
"Init": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
index aff8218855..6c32f48ded 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
@@ -10,7 +10,7 @@
"Auth": {
"listen_on": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"data_sources": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
index 24bfa2af35..acaf9219f5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
@@ -10,14 +10,14 @@
"Auth": {
"listen_on": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"Xfrin": {
"zones": [ {
"name": "example",
"master_addr": "::1",
- "master_port": 47807
+ "master_port": 56177
} ]
},
"data_sources": {
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
index c4ba1efe70..4076ea5ce6 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -10,7 +10,7 @@
"Auth": {
"listen_on": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"data_sources": {
@@ -27,7 +27,7 @@
"zones": [ {
"name": "example.org",
"master_addr": "::1",
- "master_port": 47807,
+ "master_port": 56177,
"request_ixfr": "no"
} ]
},
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
index b99f3f72be..5d44a002b4 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -10,7 +10,7 @@
"Auth": {
"listen_on": [ {
"address": "127.0.0.1",
- "port": 47806
+ "port": 56176
} ]
},
"data_sources": {
@@ -27,7 +27,7 @@
"zones": [ {
"name": "example.org",
"master_addr": "127.0.0.1",
- "master_port": 47809,
+ "master_port": 56179,
"request_ixfr": "no"
} ]
},
diff --git a/tests/lettuce/configurations/xfrout_master.conf b/tests/lettuce/configurations/xfrout_master.conf
index 755e69899b..da5381cd21 100644
--- a/tests/lettuce/configurations/xfrout_master.conf
+++ b/tests/lettuce/configurations/xfrout_master.conf
@@ -11,7 +11,7 @@
"database_file": "data/xfrout.sqlite3",
"listen_on": [ {
"address": "::1",
- "port": 47806
+ "port": 56176
} ]
},
"data_sources": {
diff --git a/tests/lettuce/data/.gitignore b/tests/lettuce/data/.gitignore
index 946228cb97..da78834b65 100644
--- a/tests/lettuce/data/.gitignore
+++ b/tests/lettuce/data/.gitignore
@@ -1,4 +1,5 @@
/inmem-xfrin.sqlite3
+/root.sqlite3
/test_nonexistent_db.sqlite3
/xfrin-before-diffs.sqlite3
/xfrin-notify.sqlite3
diff --git a/tests/lettuce/data/root.sqlite3.orig b/tests/lettuce/data/root.sqlite3.orig
new file mode 100644
index 0000000000..1bef7614dc
--- /dev/null
+++ b/tests/lettuce/data/root.sqlite3.orig
Binary files differ
diff --git a/tests/lettuce/features/ddns_system.feature b/tests/lettuce/features/ddns_system.feature
index 6747b53a07..9b3f116607 100644
--- a/tests/lettuce/features/ddns_system.feature
+++ b/tests/lettuce/features/ddns_system.feature
@@ -164,16 +164,16 @@ Feature: DDNS System
# And wait for primary stderr message XFROUT_STARTED
# And wait for primary stderr message DDNS_STARTED
- # And I start bind10 with configuration example2.org.config with cmdctl port 47804 as secondary
+ # And I start bind10 with configuration example2.org.config with cmdctl port 56174 as secondary
# And wait for secondary stderr message AUTH_SERVER_STARTED
# And wait for secondary stderr message XFRIN_STARTED
# # Sanity check
# The SOA serial for example.org should be 1234
- # The SOA serial for example.org at 127.0.0.1:47807 should be 1234
+ # The SOA serial for example.org at 127.0.0.1:56177 should be 1234
# When I use DDNS to set the SOA serial to 1235
# The DDNS response should be SUCCESS
# The SOA serial for example.org should be 1235
- # The SOA serial for example.org at 127.0.0.1:47807 should be 1235
+ # The SOA serial for example.org at 127.0.0.1:56177 should be 1235
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index 48cef20ea9..0a11a16949 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -129,8 +129,8 @@ Feature: Example feature
# where
A query for www.example.org class CH should have rcode REFUSED
A query for www.example.org to 127.0.0.1 should have rcode NOERROR
- A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
- A query for www.example.org type A class IN to 127.0.0.1:47806 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NOERROR
+ A query for www.example.org type A class IN to 127.0.0.1:56176 should have rcode NOERROR
Scenario: example.org mixed-case query
# This scenario performs a mixed-case query and checks that the
@@ -207,19 +207,19 @@ Feature: Example feature
And wait for bind10_one stderr message CMDCTL_STARTED
And wait for bind10_one stderr message AUTH_SERVER_STARTED
- And I start bind10 with configuration example2.org.config with cmdctl port 47804 as bind10_two
+ And I start bind10 with configuration example2.org.config with cmdctl port 56174 as bind10_two
And wait for bind10_two stderr message BIND10_STARTED_CC
And wait for bind10_two stderr message CMDCTL_STARTED
And wait for bind10_two stderr message AUTH_SERVER_STARTED
- A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
- A query for www.example.org to [::1]:47807 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NOERROR
+ A query for www.example.org to [::1]:56177 should have rcode NOERROR
The SOA serial for example.org should be 1234
- The SOA serial for example.org at 127.0.0.1:47806 should be 1234
- The SOA serial for example.org at ::1:47807 should be 1234
+ The SOA serial for example.org at 127.0.0.1:56176 should be 1234
+ The SOA serial for example.org at ::1:56177 should be 1234
Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/empty_db.sqlite3"}
And wait for bind10_one stderr message DATASRC_SQLITE_CONNOPEN
- A query for www.example.org to 127.0.0.1:47806 should have rcode REFUSED
- A query for www.example.org to [::1]:47807 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:56176 should have rcode REFUSED
+ A query for www.example.org to [::1]:56177 should have rcode NOERROR
diff --git a/tests/lettuce/features/inmemory_over_sqlite3.feature b/tests/lettuce/features/inmemory_over_sqlite3.feature
index 7d8ad6ddd1..2f230b9de8 100644
--- a/tests/lettuce/features/inmemory_over_sqlite3.feature
+++ b/tests/lettuce/features/inmemory_over_sqlite3.feature
@@ -12,7 +12,7 @@ Feature: In-memory zone using SQLite3 backend
The SOA serial for example.org should be 1234
Scenario: 2. In-memory datasource backed by sqlite3
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -26,18 +26,18 @@ Feature: In-memory zone using SQLite3 backend
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ A query for www.example.org to [::1]:56176 should have rcode NOERROR
"""
www.example.org. 3600 IN A 192.0.2.63
"""
- A query for mail.example.org to [::1]:47806 should have rcode NXDOMAIN
- When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ A query for mail.example.org to [::1]:56176 should have rcode NXDOMAIN
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 56177
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
Then wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
- A query for www.example.org to [::1]:47807 should have rcode NOERROR
+ A query for www.example.org to [::1]:56177 should have rcode NOERROR
The answer section of the last query response should be
"""
www.example.org. 3600 IN A 192.0.2.1
"""
- A query for mail.example.org to [::1]:47806 should have rcode NOERROR
+ A query for mail.example.org to [::1]:56176 should have rcode NOERROR
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
index 2db6c3e8de..8fcee7c41e 100644
--- a/tests/lettuce/features/queries.feature
+++ b/tests/lettuce/features/queries.feature
@@ -331,6 +331,28 @@ Feature: Querying feature
| qryreferral | 1 |
| rcode.noerror | 1 |
+ Scenario: RRSIG query
+ # Directly querying for RRSIGs should result in rcode=REFUSED.
+ Given I have bind10 running with configuration nsec3/nsec3_auth.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A dnssec query for example. type RRSIG should have rcode REFUSED
+ The last query response should have flags qr aa
+ The last query response should have edns_flags do
+ The last query response should have ancount 0
+ The last query response should have nscount 0
+ The last query response should have adcount 1
+
Scenario: SSHFP query
# We are testing one more RR type for a normal successful case
Given I have bind10 running with configuration example.org.inmem.config
@@ -412,3 +434,21 @@ Feature: Querying feature
| qryauthans | 2 |
| qrynxrrset | 1 |
| rcode.noerror | 2 |
+
+ Scenario: Querying non-existing name in root zone from sqlite3 should work
+ Given I have bind10 running with configuration root.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ bind10 module Auth should be running
+ And bind10 module Stats should be running
+ And bind10 module Resolver should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module StatsHttpd should not be running
+
+ A query for . type SOA should have rcode NOERROR
+ A query for nonexistent. type A should have rcode NXDOMAIN
+ Then wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE not AUTH_PROCESS_FAIL
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index e288aa934a..97f582cde7 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -40,7 +40,7 @@ def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
will be used. The path is relative to the base lettuce
directory.
cmdctl_port ('with cmdctl port <portnr>', optional): The port on which
- b10-cmdctl listens for bindctl commands. Defaults to 47805.
+ b10-cmdctl listens for bindctl commands. Defaults to 56175.
msgq_sockfile ('with msgq socket file', optional): The msgq socket file
that will be used for internal communication
process_name ('as <name>', optional). This is the name that can be used
@@ -59,7 +59,7 @@ def start_bind10(step, config_file, cmdctl_port, msgq_sockfile, process_name):
args.append('-c')
args.append(config_file)
if cmdctl_port is None:
- args.append('--cmdctl-port=47805')
+ args.append('--cmdctl-port=56175')
else:
args.append('--cmdctl-port=' + cmdctl_port)
if process_name is None:
@@ -126,7 +126,7 @@ def run_bindctl(commands, cmdctl_port=None, ignore_failure=False):
commands: a sequence of strings which will be sent.
cmdctl_port: a port number on which cmdctl is listening, is converted
to string if necessary. If not provided, or None, defaults
- to 47805
+ to 56175
ignore_failure(bool): if set to True, don't examin the result code
of bindctl and assert it succeeds.
@@ -135,8 +135,9 @@ def run_bindctl(commands, cmdctl_port=None, ignore_failure=False):
Fails if the return code is not 0
"""
if cmdctl_port is None:
- cmdctl_port = 47805
- args = ['bindctl', '-p', str(cmdctl_port)]
+ cmdctl_port = 56175
+
+ args = ['bindctl', '-p', str(cmdctl_port), '--csv-file-dir=configurations/' ]
bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
subprocess.PIPE, None)
for line in commands:
@@ -268,7 +269,7 @@ def config_set_command(step, name, value, cmdctl_port):
name ('configuration <name>'): Identifier of the configuration to set
value ('to <value>'): value to set it to.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
- the command to. Defaults to 47805.
+ the command to. Defaults to 56175.
Fails if cmdctl does not exit with status code 0.
"""
commands = ["config set " + name + " " + value,
@@ -282,7 +283,7 @@ def send_multiple_commands(step, cmdctl_port):
Run bindctl, and send it the given multiline set of commands.
A quit command is always appended.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
- the command to. Defaults to 47805.
+ the command to. Defaults to 56175.
Fails if cmdctl does not exit with status code 0.
"""
commands = step.multiline.split("\n")
@@ -299,7 +300,7 @@ def config_remove_command(step, name, value, cmdctl_port):
value ('value <value>'): if name is a named set, use value to identify
item to remove
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
- the command to. Defaults to 47805.
+ the command to. Defaults to 56175.
Fails if cmdctl does not exit with status code 0.
"""
cmd = "config remove " + name
@@ -317,7 +318,7 @@ def send_command(step, cmdctl_port, ignore_failure, command):
Parameters:
command ('the command <command>'): The command to send.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
- the command to. Defaults to 47805.
+ the command to. Defaults to 56175.
ignore_failure ('ignoring failure', optional): set to None if bindctl
is expected to succeed (normal case, which is the default); if it is
not None, it means bindctl is expected to fail (and it's acceptable).
diff --git a/tests/lettuce/features/terrain/nsupdate.py b/tests/lettuce/features/terrain/nsupdate.py
index 26964f5110..78e89eea83 100644
--- a/tests/lettuce/features/terrain/nsupdate.py
+++ b/tests/lettuce/features/terrain/nsupdate.py
@@ -106,7 +106,7 @@ def prepare_update(step, zone, server, port):
if port is not None:
commands[0] = commands[0] + " " + port
else:
- commands[0] = commands[0] + " 47806"
+ commands[0] = commands[0] + " 56176"
if zone is not None:
commands.append("zone " + zone)
world.nsupdate_commands = commands
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
index d585e5e044..1ba0b9325c 100644
--- a/tests/lettuce/features/terrain/querying.py
+++ b/tests/lettuce/features/terrain/querying.py
@@ -18,7 +18,7 @@
#
# query for <name> [type X] [class X] [to <addr>[:port]] should have rcode <rc>
#
-# By default, it will send queries to 127.0.0.1:47806 unless specified
+# By default, it will send queries to 127.0.0.1:56176 unless specified
# otherwise. The rcode is always checked. If the result is not NO_ANSWER,
# the result will be stored in last_query_result, which can then be inspected
# more closely, for instance with the step
@@ -221,7 +221,7 @@ def query(step, dnssec, recursive, query_name, qtype, qclass, addr, port,
addr ('to <address>', optional): The IP address of the nameserver to query.
Defaults to 127.0.0.1.
port (':<port>', optional): The port number of the nameserver to query.
- Defaults to 47806.
+ Defaults to 56176.
rcode ('should have rcode <rcode>'): The expected rcode of the answer.
"""
if qtype is None:
@@ -232,7 +232,7 @@ def query(step, dnssec, recursive, query_name, qtype, qclass, addr, port,
addr = "127.0.0.1"
addr = re.sub(r"\[(.+)\]", r"\1", addr) # convert [IPv6_addr] to IPv6_addr
if port is None:
- port = 47806
+ port = 56176
additional_arguments = []
if dnssec is not None:
additional_arguments.append("+dnssec")
@@ -254,7 +254,7 @@ def query(step, dnssec, recursive, query_name, qtype, qclass, addr, port,
def query_soa(step, query_name, address, port, serial=None):
"""
Convenience function to check the SOA SERIAL value of the given zone at
- the nameserver at the default address (127.0.0.1:47806).
+ the nameserver at the default address (127.0.0.1:56176).
Parameters:
query_name ('for <name>'): The zone to find the SOA record for.
serial ('should be <number>'): The expected value of the SOA SERIAL.
@@ -264,7 +264,7 @@ def query_soa(step, query_name, address, port, serial=None):
if address is None:
address = "127.0.0.1"
if port is None:
- port = "47806"
+ port = "56176"
query_result = QueryResult(query_name, "SOA", "IN", address, port)
assert "NOERROR" == query_result.rcode,\
"Got " + query_result.rcode + ", expected NOERROR"
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index d0ba4fe528..66727bf7ec 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -76,8 +76,12 @@ copylist = [
"configurations/xfrin/retransfer_slave.conf"],
["configurations/xfrin/retransfer_slave_notify.conf.orig",
"configurations/xfrin/retransfer_slave_notify.conf"],
+ ["configurations/root.config.orig",
+ "configurations/root.config"],
["data/inmem-xfrin.sqlite3.orig",
"data/inmem-xfrin.sqlite3"],
+ ["data/root.sqlite3.orig",
+ "data/root.sqlite3"],
["data/xfrin-before-diffs.sqlite3.orig",
"data/xfrin-before-diffs.sqlite3"],
["data/xfrin-notify.sqlite3.orig",
diff --git a/tests/lettuce/features/terrain/transfer.py b/tests/lettuce/features/terrain/transfer.py
index 0983de5505..5f219911d1 100644
--- a/tests/lettuce/features/terrain/transfer.py
+++ b/tests/lettuce/features/terrain/transfer.py
@@ -61,11 +61,11 @@ class TransferResult(object):
def parse_addr_port(address, port):
if address is None:
- address = "::1" # default address
+ address = "::1" # default address
# convert [IPv6_addr] to IPv6_addr:
address = re.sub(r"\[(.+)\]", r"\1", address)
if port is None:
- port = 47806 # default port
+ port = 56176 # default port
return (address, port)
@step('An AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
@@ -78,19 +78,19 @@ def perform_axfr(step, zone_name, address, port):
An AXFR transfer of <zone_name> [from <address>:<port>]
Address defaults to ::1
- Port defaults to 47806
+ Port defaults to 56176
"""
(address, port) = parse_addr_port(address, port)
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
world.transfer_result = TransferResult(args)
-@step('A customized AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?(?: with pose of (\d+) seconds?)?')
+@step('A customized AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?(?: with pause of (\d+) seconds?)?')
def perform_custom_axfr(step, zone_name, address, port, delay):
"""Checks AXFR transfer, and store the result in the form of internal
CustomTransferResult class, which is compatible with TransferResult.
Step definition:
- A customized AXFR transfer of <zone_name> [from <address>:<port>] [with pose of <delay> second]
+ A customized AXFR transfer of <zone_name> [from <address>:<port>] [with pause of <delay> second]
If optional delay is specified (not None), it waits for the specified
seconds after sending the AXFR query before starting receiving
@@ -144,13 +144,13 @@ def perform_ixfr(step, zone_name, serial, address, port, protocol):
An IXFR transfer of <zone_name> <serial> [from <address>:port] [over <tcp|udp>]
Address defaults to 127.0.0.1
- Port defaults to 47806
+ Port defaults to 56176
If either tcp or udp is specified, only this protocol will be used.
"""
if address is None:
address = "127.0.0.1"
if port is None:
- port = 47806
+ port = 56176
args = [ 'dig', 'IXFR=' + str(serial), '@' + str(address), '-p', str(port), zone_name ]
if protocol is not None:
assert protocol == 'tcp' or protocol == 'udp', "Unknown protocol: " + protocol
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index db4bddb905..c3b5bdbbb1 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -6,7 +6,7 @@ Feature: Xfrin
# This file is actually automatically created.
The file data/test_nonexistent_db.sqlite3 should not exist
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -24,10 +24,10 @@ Feature: Xfrin
# The DB currently doesn't know anything about the zone, so we install
# an empty zone for xfrin.
The file data/test_nonexistent_db.sqlite3 should exist
- A query for www.example.org to [::1]:47806 should have rcode REFUSED
+ A query for www.example.org to [::1]:56176 should have rcode REFUSED
Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
- When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 56177
# The data we receive contain a NS RRset that refers to three names in the
# example.org. zone. All these three are nonexistent in the data, producing
# 3 separate warning messages in the log.
@@ -40,7 +40,7 @@ Feature: Xfrin
# we can't reliably use 'wait for new'. In this case this should be the
# only occurrence of this message, so this should be okay.
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
- A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ A query for www.example.org to [::1]:56176 should have rcode NOERROR
# The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
# The following check will get these by AXFR, so the total # of RRs
@@ -53,18 +53,18 @@ Feature: Xfrin
# Now try to offer another update. However, the validation of
# data should fail. The old version shoud still be available.
- When I send bind10 the following commands with cmdctl port 47804:
+ When I send bind10 the following commands with cmdctl port 56174:
"""
config set data_sources/classes/IN[0]/params/database_file data/example.org-nons.sqlite3
config set Auth/database_file data/example.org-nons.sqlite3
config commit
"""
- Then I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ Then I send bind10 the command Xfrin retransfer example.org IN ::1 56177
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
And wait for new bind10 stderr message XFRIN_INVALID_ZONE_DATA
# We can't use 'wait for new' here; see above.
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
- A query for example.org type NS to [::1]:47806 should have rcode NOERROR
+ A query for example.org type NS to [::1]:56176 should have rcode NOERROR
And transfer result should have 13 rrs
Scenario: Transfer with TSIG
@@ -75,7 +75,7 @@ Feature: Xfrin
# non-TSIG config, add TSIG on the master side, see it fail, add TSIG
# on the slave side, then check again.
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message AUTH_SERVER_STARTED
And wait for master stderr message XFROUT_STARTED
@@ -87,7 +87,7 @@ Feature: Xfrin
Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
# Set slave config for 'automatic' xfrin
- When I set bind10 configuration Xfrin/zones to [{"master_port": 47806, "name": "example.org", "master_addr": "::1"}]
+ When I set bind10 configuration Xfrin/zones to [{"master_port": 56176, "name": "example.org", "master_addr": "::1"}]
# Make sure it is fully open
When I send bind10 the command Xfrin retransfer example.org
@@ -96,7 +96,7 @@ Feature: Xfrin
And wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
# First to master, a transfer should then fail
- When I send bind10 the following commands with cmdctl port 47804:
+ When I send bind10 the following commands with cmdctl port 56174:
"""
config add tsig_keys/keys "example.key.:c2VjcmV0"
config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "::1", "key": "example.key."}]
@@ -127,7 +127,7 @@ Feature: Xfrin
# do here).
The file data/test_nonexistent_db.sqlite3 should not exist
- Given I have bind10 running with configuration xfrin/retransfer_master_nons.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master_nons.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -144,10 +144,10 @@ Feature: Xfrin
# Now we use the first step again to see if the file has been created,
# then install empty zone data
The file data/test_nonexistent_db.sqlite3 should exist
- A query for www.example.org to [::1]:47806 should have rcode REFUSED
+ A query for www.example.org to [::1]:56176 should have rcode REFUSED
Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
- When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
+ When I send bind10 the command Xfrin retransfer example.org IN ::1 56177
# It should complain once about invalid data, then again that the whole
# zone is invalid and then reject it.
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
@@ -157,7 +157,7 @@ Feature: Xfrin
# The zone still doesn't exist as it is rejected.
# FIXME: This step fails. Probably an empty zone is created in the data
# source :-|. This should be REFUSED, not SERVFAIL.
- A query for www.example.org to [::1]:47806 should have rcode SERVFAIL
+ A query for www.example.org to [::1]:56176 should have rcode SERVFAIL
# TODO:
# * IXFR - generate an sqlite db that contains the journal. Check it was
@@ -170,7 +170,7 @@ Feature: Xfrin
# scenario. Just this time, the master contains the differences table
# and the slave has a previous version of the zone, so we use the IXFR.
- Given I have bind10 running with configuration xfrin/retransfer_master_diffs.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master_diffs.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -184,20 +184,20 @@ Feature: Xfrin
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ A query for example. type SOA to [::1]:56176 should have rcode NOERROR
The answer section of the last query response should be
"""
example. 3600 IN SOA ns1.example. hostmaster.example. 94 3600 900 7200 300
"""
# To invoke IXFR we need to use refresh command
- When I send bind10 the command Xfrin refresh example. IN ::1 47807
+ When I send bind10 the command Xfrin refresh example. IN ::1 56177
Then wait for new bind10 stderr message XFRIN_GOT_INCREMENTAL_RESP
Then wait for new bind10 stderr message XFRIN_IXFR_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
# This can't be 'wait for new'
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
- A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ A query for example. type SOA to [::1]:56176 should have rcode NOERROR
The answer section of the last query response should be
"""
example. 3600 IN SOA ns1.example. hostmaster.example. 100 3600 900 7200 300
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index 2f43dfdfe6..bba4f83ff3 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -7,7 +7,7 @@ Feature: Xfrin incoming notify handling
server or slave server is unreachable.
Scenario: Handle incoming notify
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -22,12 +22,12 @@ Feature: Xfrin incoming notify handling
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test1 for Xfrout statistics
#
- check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ check initial statistics not containing example.org for Xfrout with cmdctl port 56174 except for the following items
| item_name | min_value | max_value |
| socket.unixdomain.open | 0 | 1 |
# Note: .Xfrout.socket.unixdomain.open can be either expected to
@@ -49,7 +49,7 @@ Feature: Xfrin incoming notify handling
# reason is same as the case of .Xfrout.socket.unixdomain.open. as described
# above.
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
# From this point we can't reliably 'wait for new' because the ordering
@@ -61,7 +61,7 @@ Feature: Xfrin incoming notify handling
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ A query for www.example.org to [::1]:56176 should have rcode NOERROR
# Make sure handling statistics command handling checked below is
# after this query
And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
@@ -73,14 +73,14 @@ Feature: Xfrin incoming notify handling
#
# wait until the last stats requesting is finished
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
# note that this does not 100% guarantee the stats updated Xfrout
# statistics. But there doesn't seem to be a better log message that
# suggests this event.
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.notifyoutv6 | 1 |
@@ -88,7 +88,7 @@ Feature: Xfrin incoming notify handling
| example.org..notifyoutv6 | 1 |
| example.org..xfrreqdone | 1 |
- When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics socket of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
| item_name | item_value |
| open | 1 |
@@ -127,7 +127,7 @@ Feature: Xfrin incoming notify handling
# Test for handling incoming notify only in IPv4
#
Scenario: Handle incoming notify (IPv4)
- Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -142,12 +142,12 @@ Feature: Xfrin incoming notify handling
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NXDOMAIN
#
# Test1 for Xfrout statistics
#
- check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ check initial statistics not containing example.org for Xfrout with cmdctl port 56174 except for the following items
| item_name | min_value | max_value |
| socket.unixdomain.open | 0 | 1 |
# Note: See above about .Xfrout.socket.unixdomain.open.
@@ -161,7 +161,7 @@ Feature: Xfrin incoming notify handling
| axfr_running | 0 | 1 |
# Note: See above about soa_in_progress and axfr_running of Xfrin
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
# From this point we can't reliably 'wait for new' because the ordering
@@ -173,7 +173,7 @@ Feature: Xfrin incoming notify handling
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NOERROR
# Make sure handling statistics command handling checked below is
# after this query
And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
@@ -185,14 +185,14 @@ Feature: Xfrin incoming notify handling
#
# wait until the last stats requesting is finished
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
# note that this does not 100% guarantee the stats updated Xfrout
# statistics. But there doesn't seem to be a better log message that
# suggests this event.
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value |
| _SERVER_.notifyoutv4 | 1 |
@@ -200,7 +200,7 @@ Feature: Xfrin incoming notify handling
| example.org..notifyoutv4 | 1 |
| example.org..xfrreqdone | 1 |
- When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics socket of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
| item_name | item_value |
| open | 1 |
@@ -239,7 +239,7 @@ Feature: Xfrin incoming notify handling
# Test for Xfr request rejected
#
Scenario: Handle incoming notify (XFR request rejected)
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -254,12 +254,12 @@ Feature: Xfrin incoming notify handling
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test1 for Xfrout statistics
#
- check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ check initial statistics not containing example.org for Xfrout with cmdctl port 56174 except for the following items
| item_name | min_value | max_value |
| socket.unixdomain.open | 0 | 1 |
# Note: See above about .Xfrout.socket.unixdomain.open.
@@ -277,14 +277,14 @@ Feature: Xfrin incoming notify handling
# set transfer_acl rejection
# Local xfr requests from Xfrin module would be rejected here.
#
- When I send bind10 the following commands with cmdctl port 47804
+ When I send bind10 the following commands with cmdctl port 56174
"""
config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "::1"}]
config commit
"""
last bindctl output should not contain Error
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
# can't use 'wait for new' below.
@@ -294,7 +294,7 @@ Feature: Xfrin incoming notify handling
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test3 for Xfrout statistics
@@ -303,11 +303,11 @@ Feature: Xfrin incoming notify handling
#
# wait until the last stats requesting is finished
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value | min_value | max_value |
| _SERVER_.notifyoutv6 | 1 | | |
@@ -318,7 +318,7 @@ Feature: Xfrin incoming notify handling
# up to 3. See this for details
# http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
- When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics socket of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
| item_name | item_value |
| open | 1 |
@@ -355,7 +355,7 @@ Feature: Xfrin incoming notify handling
# Test for Xfr request rejected in IPv4
#
Scenario: Handle incoming notify (XFR request rejected in IPv4)
- Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -370,12 +370,12 @@ Feature: Xfrin incoming notify handling
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NXDOMAIN
#
# Test1 for Xfrout statistics
#
- check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ check initial statistics not containing example.org for Xfrout with cmdctl port 56174 except for the following items
| item_name | min_value | max_value |
| socket.unixdomain.open | 0 | 1 |
# Note: See above about .Xfrout.socket.unixdomain.open.
@@ -393,14 +393,14 @@ Feature: Xfrin incoming notify handling
# set transfer_acl rejection
# Local xfr requests from Xfrin module would be rejected here.
#
- When I send bind10 the following commands with cmdctl port 47804
+ When I send bind10 the following commands with cmdctl port 56174
"""
config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "127.0.0.1"}]
config commit
"""
last bindctl output should not contain Error
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
# can't use 'wait for new' below.
@@ -410,7 +410,7 @@ Feature: Xfrin incoming notify handling
Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+ A query for www.example.org to 127.0.0.1:56176 should have rcode NXDOMAIN
#
# Test3 for Xfrout statistics
@@ -418,12 +418,12 @@ Feature: Xfrin incoming notify handling
# check statistics change
#
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
# wait until stats request at least after NOTIFY_OUT_REPLY_RECEIVED
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | item_value | min_value | max_value |
| _SERVER_.notifyoutv4 | 1 | | |
@@ -434,7 +434,7 @@ Feature: Xfrin incoming notify handling
# up to 3. See this for details
# http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
- When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics socket of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
| item_name | item_value |
| open | 1 |
@@ -471,7 +471,7 @@ Feature: Xfrin incoming notify handling
# Test for unreachable slave
#
Scenario: Handle incoming notify (unreachable slave)
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -479,7 +479,7 @@ Feature: Xfrin incoming notify handling
And wait for master stderr message ZONEMGR_STARTED
And wait for master stderr message STATS_STARTING
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
Then wait for new master stderr message NOTIFY_OUT_TIMEOUT
@@ -490,18 +490,18 @@ Feature: Xfrin incoming notify handling
# check statistics change
#
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
# wait until stats request at least after NOTIFY_OUT_TIMEOUT
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
| item_name | min_value | max_value |
| _SERVER_.notifyoutv6 | 1 | 5 |
| example.org..notifyoutv6 | 1 | 5 |
- When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
+ When I query statistics socket of bind10 module Xfrout with cmdctl port 56174
The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
| item_name | item_value |
| open | 1 |
@@ -510,7 +510,7 @@ Feature: Xfrin incoming notify handling
# Test for NOTIFY that would result in NOTAUTH
#
Scenario: Handle incoming notify that does match authoritative zones
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -529,7 +529,7 @@ Feature: Xfrin incoming notify handling
# replace master's data source with unmatched zone for slave's zone.
# we restart Xfrout to make it sure.
#
- When I send bind10 the following commands with cmdctl port 47804
+ When I send bind10 the following commands with cmdctl port 56174
"""
config set data_sources/classes/IN[0]/params/database_file data/ixfr-out/zones.sqlite3
config set Auth/database_file data/ixfr-out/zones.sqlite3
@@ -540,20 +540,20 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
And wait for new master stderr message XFROUT_STARTED
- A query for www.example.com to [::1]:47806 should have rcode REFUSED
+ A query for www.example.com to [::1]:56176 should have rcode REFUSED
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.com IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.com IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY_NOTAUTH
Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.com to [::1]:47806 should have rcode REFUSED
+ A query for www.example.com to [::1]:56176 should have rcode REFUSED
#
# Test for NOTIFY that's not in the secondaries list
#
Scenario: Handle incoming notify that is not in the secondaries list
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -580,22 +580,22 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
And wait for new bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
Then wait for new bind10 stderr message ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY
Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test for NOTIFY when zonemgr is not running
#
Scenario: Handle incoming notify while zonemgr is not running
- Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 56174 as master
And wait for master stderr message BIND10_STARTED_CC
And wait for master stderr message CMDCTL_STARTED
And wait for master stderr message AUTH_SERVER_STARTED
@@ -620,15 +620,15 @@ Feature: Xfrin incoming notify handling
last bindctl output should not contain "error"
And wait for new bind10 stderr message BIND10_PROCESS_ENDED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
- When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ When I send bind10 with cmdctl port 56174 the command Xfrout notify example.org IN
Then wait for master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
Then wait for new bind10 stderr message AUTH_ZONEMGR_NOTEXIST not AUTH_ZONEMGR_ERROR
Then wait for master stderr message NOTIFY_OUT_TIMEOUT not NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test for unreachable master
@@ -642,7 +642,7 @@ Feature: Xfrin incoming notify handling
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to [::1]:56176 should have rcode NXDOMAIN
#
# Test1 for Xfrin statistics
diff --git a/tests/lettuce/features/xfrout_bind10.feature b/tests/lettuce/features/xfrout_bind10.feature
index 7f4e4de7e8..e2ca7be59a 100644
--- a/tests/lettuce/features/xfrout_bind10.feature
+++ b/tests/lettuce/features/xfrout_bind10.feature
@@ -35,5 +35,5 @@ Feature: Xfrout
And wait for bind10 stderr message XFROUT_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- When I do a customized AXFR transfer of example.org from [::1]:47806 with pose of 5 seconds
+ When I do a customized AXFR transfer of example.org from [::1]:56176 with pause of 5 seconds
Then transfer result should have 50001 rrs
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 3751e2f0e3..b317eff969 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -564,11 +564,8 @@ TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
isc_throw(InvalidOperation, "packet timestamp not set");;
}
time_period elapsed_period(pkt1_time, pkt2_time);
- if (elapsed_period.is_null()) {
- isc_throw(InvalidOperation, "unable to calculate time elapsed"
- " between packets");
- }
- return(elapsed_period.length().total_milliseconds());
+ return (elapsed_period.is_null() ? 0 :
+ elapsed_period.length().total_milliseconds());
}
int
@@ -780,8 +777,7 @@ TestControl::openSocket() const {
} else if (options.getIpVersion() == 6) {
// If remote address is multicast we need to enable it on
// the socket that has been created.
- asio::ip::address_v6 remote_v6 = remoteaddr.getAddress().to_v6();
- if (remote_v6.is_multicast()) {
+ if (remoteaddr.isV6Multicast()) {
int hops = 1;
int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&hops, sizeof(hops));
@@ -1643,7 +1639,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
/// Set client address.
asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
- if (!yiaddr.getAddress().is_v4()) {
+ if (!yiaddr.isV4()) {
isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
" IPv4 address");
}
@@ -1751,7 +1747,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
/// Set client address.
asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
- if (!yiaddr.getAddress().is_v4()) {
+ if (!yiaddr.isV4()) {
isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
" IPv4 address");
}
diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h
index 5a7ef48d1a..a0746a0ee0 100644
--- a/tests/tools/perfdhcp/test_control.h
+++ b/tests/tools/perfdhcp/test_control.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -943,6 +943,11 @@ protected:
/// \brief Calculate elapsed time between two packets.
///
+ /// This function calculates the time elapsed between two packets. If
+ /// the timestamp of the pkt2 is greater than timestamp of the pkt1,
+ /// the positive value is returned. If the pkt2 timestamp is equal or
+ /// less than pkt1 timestamp, 0 is returned.
+ ///
/// \tparam T Pkt4Ptr or Pkt6Ptr class.
/// \param pkt1 first packet.
/// \param pkt2 second packet.