BIND 10 trac1687, updated. 41040dc27da86909c61187b8951f63f9d71c3dc7 [trac1687]Revert "[trac1687] make man page generation on by default"
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu May 31 20:00:14 UTC 2012
The branch, trac1687 has been updated
via 41040dc27da86909c61187b8951f63f9d71c3dc7 (commit)
via e6cccc208317b2c175f28bc8fee16fe4e4d12c79 (commit)
via ea903ad8903d356528b183bde0d25980a75898d8 (commit)
via fcf1ad8c2b2966cf3e9e0781aa059e159a5388f6 (commit)
via 02f76675c5728649d65e63c29f0603d465730281 (commit)
via 92cdf7773ea4631eda41a15b5b5b21a4926ff391 (commit)
via 98c723ce92f98e2982f61fbbb4c12b74c4aed87d (commit)
via c5b7c61747858fb2fb34e62f4f04d4be663f7adb (commit)
via afc0fe7345aa86ba3eaebf1bba8f62db64760c1f (commit)
via f89b4428468decd82132b0e0a02f2dc171fd690c (commit)
via 24fb93177f6d50c2713374ef11432510c4da6a0e (commit)
via 6002a070bac4531ece1127f32434b4bc565952d8 (commit)
via 20a21a87777944fea10cf615b0b54d6e96df00ae (commit)
via 195c7eaef0d31ad0a37ad4bf53621506a7ffe02d (commit)
via 031eaf1e2378242b5ebd2804347cba5843408eab (commit)
via 266ddad13b59fe73124e35af6f3a3c62f0f743f5 (commit)
via e8cabc67f199b59704c0064521ddd6fdaeb438d1 (commit)
via 00d06904569e27ba56975b60906022585a43ab4e (commit)
via a45a2bd0f8fdaf0484ab88797775f61f85cd9a03 (commit)
via e60af9fa16a6094d2204f27c40a648fae313bdae (commit)
via a21935594e3705d3ac1299de18a685fca15ff3c7 (commit)
via 52993e952156979f0b0f3142e6959e6769da1f70 (commit)
via b128daebb970c0b09680e1d1c485af8dcfc4ab55 (commit)
via c4680d25344d1909f071117296b2f9791d701263 (commit)
via 6b838bbd27ba453e045eb5a89ab800969fbeebdf (commit)
via 18ecc62f5d12a117da3e497aa802226395ee7e92 (commit)
via fad3309ccd7794b44b8ee38d5ec0d189e1b4f3b0 (commit)
via aa7d9f1f307b301f97e4381930aa710f0ce2e3c6 (commit)
via 900fc8b420789a8c636bcf20fdaffc60bc1041e0 (commit)
via 39dcfd79bb1b79812838c7b2f1eb146519f9cf7e (commit)
via 04a793d3454490fe8aa05d91185c7bc59ff30c97 (commit)
via 8d56105742f3043ed4b561f26241f3e4331f51dc (commit)
via 7401fd9fbbe95b310165bd34da5b27359caf1c66 (commit)
via cd77eba438268ed3624a8d1c6b859baf84d993e5 (commit)
via b75e92ded404a7b0a7262ae6f0403813261e8a1a (commit)
via b35d3159e812712e47d59db80ae0115e58cd5c82 (commit)
via 948f12089ccfd8e77c568568ed78153dd926c646 (commit)
via a3f8c40ba785618482a17a3ee2cf8edc65e37d73 (commit)
via f4d8dca7f015aab64778188a428799aed4b5aae6 (commit)
via 3f32b68d6f2d0f02af6cebac046a604751ad4dcc (commit)
via 21d3d1aa2eba8be0c5260b562c2804f12951bb7d (commit)
via 72670be745bac050e9479a4363a1d5cfb6bc207b (commit)
via dbea789870bab675f5cfe79e903cc76cce4350d1 (commit)
via ba65fbfad4491d439714a05b7ce654aa649a9b46 (commit)
via 1171a27ad52ba03161e66465e4cf5f5ae93a6628 (commit)
via b94f49bf264e058dac14287eca360c82edb0071d (commit)
via f77a1e4539afd8dea0a1db15d46d85b810c5c4ee (commit)
via eacdcb4322f7fc89f35af526768bf42403ad09a6 (commit)
via c73ca19a68d8e55dd3c07e4a92a4132f4f075892 (commit)
via f47d81ec579d815ecddf8043fa6ada63df1cae16 (commit)
via 73b30ba80607b3e94b2becab17dd810e1561124a (commit)
via c4e3386e7c0b60f46ba4283dfdea069437fc3932 (commit)
via d6451dba2ce9ad4b2cf5ef63ff312136542abe4e (commit)
via 518e4673c218e2833cbeadb1a8b1cb6e8aa04f26 (commit)
via b14e2801e5f173f2eb88cbb5cb6717c1e224de18 (commit)
via 84ed05352881fca810292b827867dcd08f256cbe (commit)
via 55a817bff19c1b61e2ce32d22a743d4a3073036c (commit)
via f621147e9399d1b8199dbe4be92e83f356c066b3 (commit)
via 2801bd9baadb96c2965b2cddc729e9ac2ec6dbb0 (commit)
via 5d228bec6c0802a6b0ff19618745257c8f5b0af4 (commit)
via 237f4b641cf9fe63d28a5f283511c221bbb61cb9 (commit)
via 86daa84368ac9a61c6df7c4557f701a254d1a440 (commit)
via be4044ab9ca0163c7a736a1baa045f3b9192f11a (commit)
via 77a9d4c2d5b75e0f636d7d01f7d03999c18acf77 (commit)
via b9a9623ae1ef39893ff245d7a89e4083626ee714 (commit)
via 35ad857d5fa798fd632958e0ee8a2150f002a0a1 (commit)
via d8f2ae1c3e671294f01e737edc71bc78eb0e7d1f (commit)
via 7a9e74102707dce06920e94bf168b92aad894d5d (commit)
via c65a54347a7bceee54531a6a81209d1ec6ceb82b (commit)
via 848be350a53e835929a536d295d0fc3ee12a2376 (commit)
via be101587eb7b91a7e6604db12dbeb8725390a940 (commit)
via b5bfaf8c7da2331e4857b3586e479417ec7b8372 (commit)
via 1b5e0412294dcf0bc499c4fd288db0f6d88c4ba5 (commit)
via 788dbb80f4b0f08fa939ca7421a6dcecacda7120 (commit)
via ffdd7099e20354d4d0cb52740154f176066f9ccb (commit)
via 24c4238bacdfd2bb5804028f1efc3d7352e7558d (commit)
via 005a08df4279a119671c58698faf6e86fbc9fded (commit)
via 7d9bb0957c7eeb3d770befc75b9dd33667467269 (commit)
via cd6a1513bb1657cbeb79b26092cf74ec7f442143 (commit)
via d10df14d440510f55e29de14572395cbb374f921 (commit)
via 1aea433b99c27bb72248eea4ea7b115c11e3f57d (commit)
via 847501f6af72b45ad2521cd810170f7701c65002 (commit)
via 71e39be788efae3877e161914483d22e4fd84f71 (commit)
via 39fee4a9b55d84275c3fa6535e2c2bbb4a311f44 (commit)
via 9fee45c66fd023a5ffa15455f0f58721c5830e9e (commit)
via 12b5bdfc8b484bdfba69d6d93e7aa64ce147d6fe (commit)
via 7055c9749286b4677e7cad0662b90011f2144f92 (commit)
via b31ea61475066258008d7bad46f80df76d82255a (commit)
via c5ecbb8dfcc16802ff2550ab0a124584bf204af9 (commit)
via 6296a0882a1161e92ad629f835a46f4efed5d70d (commit)
via 7fcb34b48fcb6bd560e228a4ed68c7c53da7b02c (commit)
via fd12fc9298c6015c90321c8b9144ec2565b24dd0 (commit)
via d81f573764fe2df0006527145e439893872e6c69 (commit)
via 190b91b0701773f162424423789ee1d12fbc3260 (commit)
via 3aa296cf17e158576175ca0c349a6dee19a97af1 (commit)
via 434d67fede28c9e19127395b115dc3551d25692b (commit)
via 18faaf148ad6371bac640b72c3983336697af48d (commit)
via d70c06df29e24e8a7a258728ce419e27372c91f9 (commit)
via 634994e89a83dac53318ac32dc9a78688d5c5129 (commit)
via fedb6edd4f302d8be91ea704ac71ded40ed921f9 (commit)
via e2706e3bf587ca97983e0115e998904d9d0cc674 (commit)
via d50547b048f15666f637e47df6ae5854c01102c7 (commit)
via dd418d92a81da33ab22b97d3316205ed5e1ad7ec (commit)
via 40f1d423fdc78bedcfcaa15ffd237307dc26760f (commit)
via fd063d02153814e1ca2e63066b69b8cbf2111b20 (commit)
via 0656026efd474564a43569fc2b7bc40543adb89f (commit)
via df579c404c308a60470a6c05701af590c848726d (commit)
via e08d863413670e210a885eda393afc11b4cc94c5 (commit)
via f1e5916f4fe8f99abb7efc39732376f92b576831 (commit)
via 9bc7701597d68fdb865709a2684336e2d908d878 (commit)
via 9b85fc436278ba51beb0c7d7f358b0e519948845 (commit)
via 3bd7f2b700c2ff6c6111a672baa291d5e204c696 (commit)
via d6f64f5a6b1c524e6b6fd91151df7dee98add719 (commit)
via 2f35b1b6db5b7e9f5849a79d0342c316048b13b7 (commit)
via 9cffd7d9f5dc6c9c2909f9569c2f4c78aa71bdbd (commit)
via 2be06486e504e522f9ef39586fe7fb5947c33872 (commit)
via a5171dfc73c99aaa7d8191d309d9808dca19b49b (commit)
via 24be5d0ee863ab1d4ed6f29e3f06506c7d8753eb (commit)
via 6fb56baf17fbb2d8e666bfd7c16138f2a3ae5a44 (commit)
via 38d2dadf3b24367b9d967d23fe700899ddd752f4 (commit)
via 0e0d28e6bf3276aaf59c1c9da97e1bee538eae09 (commit)
via 7e245132c9d7e2e49a23ebf738ca5d9d229dda30 (commit)
via 9915ecdc183e64e22ba51f233d1667ad3091972a (commit)
via ad6c12af58714d9c72f7726a598b24fc81ba03fd (commit)
via 921405b03235bb9f610f3f432f3a59b4fb598b1b (commit)
via a53adb0bfd419dfcbc8af2750c1fc618b8e91768 (commit)
via 0e0005991f6c606751ffbce90a285a03f714aa37 (commit)
via 58be95ac3fcb9f498a4dc1d02a2e693068c97039 (commit)
via b726fc55cdecdbc7f335e5005a7f4b078c36f979 (commit)
via 375165752c897f516619945c762a5ea645d55ce7 (commit)
via 2570f2737d669b611ec77234de1889939c0e76b2 (commit)
via 5cda9e7282db2eca7c9bb7e2805bb03ef6ad616e (commit)
via ccb3b1adf76e4a2678e76f77e29e64c96bdd4304 (commit)
via d8aee4ee79f801022b5d306316fea34423283b5c (commit)
via bc6b92a8b79d43fb9dfa530cbbc94618dc39df79 (commit)
via ff8a54c66f7c0fadcceb18aa6b6d33c0cfbd615b (commit)
via 410df5ee446d69d53ca45e21307d07c54b5b2b76 (commit)
via d18d92e4e6a83f370258801c2c05caf15cdb6322 (commit)
via 35a0237e8d8263e32717c64f1b16b7bf59990ba7 (commit)
via 4568f3ae526352f65e3785fa46e0589afa36ff85 (commit)
via 7538a37ca0a746795782ccc1ae27db6794266502 (commit)
via 483921a7f9c8afd22aa8f86dcb7b19a94514fb0d (commit)
via 7433c00e2470bea681abcdb7e536692f8c9b2ae6 (commit)
via 2baf46246bd97966b9e6463dba9834b7122b232b (commit)
via 13838d31a8841514c5103844088ac63913d14b0e (commit)
via b3c7d1eaf3835c8bc13a7cc136b85e79aaf16934 (commit)
via 51f3a86709293442f3bb04f775ae42d7d24b03dd (commit)
via 12c0fa4fd0676ab17616d34a522d526f8139586a (commit)
via 609581d6dd36f50ad9118b2869756c2f26c5afd4 (commit)
via d74f9a69b7a7c2f80adc61be1e4a38338d24558f (commit)
via 1455f670ced8213e01848e24e3a2d994ed16b74d (commit)
via 6d4b2e6b54ae93bcd438e253573b62c11535b436 (commit)
via a2be2f5cfe81a85df13a0ea977af6a9bddf3040b (commit)
via 4aea6b74d6a48ee9e3666745ed7da416b31c2728 (commit)
via d06b9b84af34e84cadab87920fc1742f8b029d7c (commit)
via 132d530f80654677dfc10e9a0f88edd272666385 (commit)
via b174ac98fbe2c9bae96edd0faf2e8884cbbac4fd (commit)
via 4bdecaadf9b845af3c2b2b11213539a4a0226284 (commit)
via a43b1b6641de54cc27e7abbf40f0a49c2d9d617a (commit)
via 5f474b3308a695702e8b9875641bfc378656d035 (commit)
via c778e983a18659a86333aa626ad500b47a9ab26c (commit)
via 743fa2c222467587b16bf3c94746ea9e150ce479 (commit)
via b25872688315e81de52a5a2c2d12a626b88a8640 (commit)
via 194808061e1ab60bfabc8683e9f1f779459af0e7 (commit)
via 5588e70dc9d3b19af357d07639aee92069afa7fd (commit)
via 59506a667e333362966b4091cb24c10ca4605ec1 (commit)
via 9246111fe4cb6240ed82483e4ae57b2eaf2da523 (commit)
via 94fc335cc34ceb93bbc8e7caa66174d6c51038b8 (commit)
via 4c11d7a7cb7315255ce893d43be15cfaa9aa82af (commit)
via d4e28625829cd11e16a93f8ce786b77544d6af1c (commit)
via 51b0205f48ad15ae28eafaa63890d3a671acc595 (commit)
via 434ca15428a07aad9012b140724cb6ee1cd971a7 (commit)
via 480d50eef89a78c5ed326fa392254aeb2ec6df06 (commit)
via b04764bc6d95ffa182555d626b2dee91344e958a (commit)
via b6453630ee6655c325bd1e833bc34dc4f9a664b2 (commit)
via adbe1bace4e5d7bb7b063a01f769b0ae65362c2e (commit)
via 80c031beb751c45caed9faf239570e13c2aa5459 (commit)
via 4a4d5b700e5a3f2b937b61d4ff2e873278c0db59 (commit)
via 75fa2da75c4222f430efc01a3becbdbf9cff100d (commit)
via f3e8996dc522d21b685529310b1cea25cb87079b (commit)
via 2025f34658956271cf1850bba7c9fa15fe690a3b (commit)
via ef0e888aa8f4b016c1f63f0394d8d7246473b234 (commit)
via 57346e7268b5406e87e8e46b7015fed601813120 (commit)
via a7557c12f65bb21145a117df4770863737d4acee (commit)
from bf8328d7f90a94ef512b3b0d9de517650bef1a07 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 41040dc27da86909c61187b8951f63f9d71c3dc7
Author: Jeremy C. Reed <jreed at ISC.org>
Date: Thu May 31 14:59:24 2012 -0500
[trac1687]Revert "[trac1687] make man page generation on by default"
This reverts commit 682df53277cfb545a1ba34a3a2198cb555cca6f5.
Decided to not enable documentation by default for now.
Need to handle build machines and make sure the doc generation
environment is in place first.
commit e6cccc208317b2c175f28bc8fee16fe4e4d12c79
Author: Jeremy C. Reed <jreed at ISC.org>
Date: Thu May 31 14:16:46 2012 -0500
[trac1687] don't redefine docdir
It was wrong anyways. Just use what configure provides.
And no installation of guide happens yet anyways.
commit ea903ad8903d356528b183bde0d25980a75898d8
Author: Jeremy C. Reed <jreed at ISC.org>
Date: Thu May 31 14:16:08 2012 -0500
[trac1687] remove two more docs from repo
commit fcf1ad8c2b2966cf3e9e0781aa059e159a5388f6
Merge: bf8328d 02f7667
Author: Jeremy C. Reed <jreed at ISC.org>
Date: Thu May 31 13:44:14 2012 -0500
[trac1687]Merge branch 'master' into trac1687
ignoring changes to two generated man pages.
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 11 +
configure.ac | 7 +-
doc/guide/Makefile.am | 1 -
doc/guide/bind10-guide.xml | 18 +
src/bin/auth/Makefile.am | 1 +
src/bin/auth/auth_messages.mes | 14 +
src/bin/auth/auth_srv.cc | 162 ++++-
src/bin/auth/auth_srv.h | 11 +-
src/bin/auth/benchmarks/query_bench.cc | 8 +-
src/bin/auth/common.cc | 20 +-
src/bin/auth/common.h | 14 +
src/bin/auth/main.cc | 5 +-
src/bin/auth/spec_config.h.pre.in | 3 +-
src/bin/auth/tests/Makefile.am | 6 +-
src/bin/auth/tests/auth_srv_unittest.cc | 174 ++++-
src/bin/auth/tests/command_unittest.cc | 5 +-
src/bin/auth/tests/common_unittest.cc | 50 +-
src/bin/auth/tests/config_unittest.cc | 5 +-
src/bin/bind10/bind10.xml | 2 +-
src/bin/bindctl/command_sets.py | 1 -
src/bin/cfgmgr/b10-cfgmgr.xml | 2 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 1 -
src/bin/dbutil/b10-dbutil.8 | 92 ---
src/bin/dbutil/dbutil.py.in | 2 +-
src/bin/dhcp4/Makefile.am | 8 +-
src/bin/dhcp4/main.cc | 25 +-
src/bin/dhcp4/tests/Makefile.am | 17 +-
src/bin/dhcp4/tests/dhcp4_test.py | 170 +++++
src/bin/dhcp6/Makefile.am | 16 +-
src/bin/dhcp6/dhcp6_srv.cc | 2 +-
src/bin/dhcp6/interfaces.txt | 10 -
src/bin/dhcp6/main.cc | 23 +-
src/bin/dhcp6/tests/Makefile.am | 13 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 11 +-
src/bin/dhcp6/tests/dhcp6_test.py | 154 ++++-
src/bin/resolver/b10-resolver.8 | 140 ----
src/bin/sockcreator/Makefile.am | 2 +-
src/lib/asiolink/io_endpoint.cc | 24 +-
src/lib/asiolink/io_endpoint.h | 30 +-
src/lib/asiolink/tests/Makefile.am | 4 +-
src/lib/asiolink/tests/io_endpoint_unittest.cc | 61 +-
src/lib/cache/tests/message_entry_unittest.cc | 2 +
src/lib/cc/tests/Makefile.am | 2 +-
src/lib/config/Makefile.am | 5 +
src/lib/config/ccsession.cc | 95 +++
src/lib/config/ccsession.h | 132 +++-
src/lib/config/module_spec.cc | 2 +-
src/lib/config/tests/ccsession_unittests.cc | 283 +++++++-
src/lib/config/tests/fake_session.cc | 8 +-
src/lib/config/tests/fake_session.h | 2 +-
src/lib/datasrc/database.cc | 27 +-
src/lib/datasrc/datasrc_messages.mes | 11 +-
src/lib/datasrc/rbnode_rrset.h | 2 +-
src/lib/datasrc/tests/Makefile.am | 5 +-
src/lib/dhcp/iface_mgr.cc | 45 +-
src/lib/dhcp/tests/Makefile.am | 1 -
src/lib/dhcp/tests/iface_mgr_unittest.cc | 73 +--
src/lib/dns/message.cc | 38 ++
src/lib/dns/message.h | 6 +
src/lib/dns/python/message_python.cc | 27 +
src/lib/dns/python/message_python_inc.cc | 17 +
src/lib/dns/python/name_python.cc | 10 +-
src/lib/dns/python/pydnspp.cc | 9 +
src/lib/dns/python/rrclass_python.cc | 9 +-
src/lib/dns/python/tests/message_python_test.py | 20 +
src/lib/dns/python/tests/name_python_test.py | 22 +
src/lib/dns/python/tests/rrclass_python_test.py | 8 +
src/lib/dns/python/tests/rrset_python_test.py | 9 +
.../dns/rdata/generic/detail/nsec3param_common.cc | 1 -
src/lib/dns/rrset.cc | 32 +-
src/lib/dns/rrset.h | 8 +-
src/lib/dns/tests/Makefile.am | 4 +-
src/lib/dns/tests/message_unittest.cc | 7 +
src/lib/dns/tests/rrset_unittest.cc | 48 +-
src/lib/dns/tests/testdata/Makefile.am | 1 +
src/lib/dns/tests/testdata/rrset_toWire3 | 12 +
src/lib/dns/tests/testdata/rrset_toWire4 | 12 +
src/lib/log/log_formatter.cc | 7 +
src/lib/log/log_formatter.h | 31 +-
src/lib/log/tests/log_formatter_unittest.cc | 21 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/config/cfgmgr.py | 2 +-
src/lib/python/isc/config/cfgmgr_messages.mes | 9 +-
src/lib/python/isc/datasrc/finder_python.cc | 3 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 2 +-
src/lib/python/isc/ddns/Makefile.am | 23 +
src/lib/python/isc/{bind10 => ddns}/__init__.py | 0
src/lib/python/isc/ddns/libddns_messages.mes | 142 ++++
src/lib/python/isc/ddns/logger.py | 121 ++++
src/lib/python/isc/ddns/session.py | 395 ++++++++++++
src/lib/python/isc/ddns/tests/Makefile.am | 28 +
src/lib/python/isc/ddns/tests/session_tests.py | 669 +++++++++++++++++++
src/lib/python/isc/ddns/tests/zone_config_tests.py | 166 +++++
src/lib/python/isc/ddns/zone_config.py | 101 +++
src/lib/python/isc/log/log.cc | 10 +-
src/lib/python/isc/log_messages/Makefile.am | 2 +
.../python/isc/log_messages/libddns_messages.py | 1 +
src/lib/python/isc/xfrin/diff.py | 183 +++++-
src/lib/python/isc/xfrin/tests/diff_tests.py | 108 +++-
.../server_common/tests/socket_requestor_test.cc | 2 +
src/lib/statistics/counter.cc | 14 +
src/lib/statistics/counter.h | 14 +
src/lib/statistics/counter_dict.cc | 14 +
src/lib/statistics/counter_dict.h | 14 +
src/lib/testutils/srv_test.cc | 17 +-
src/lib/testutils/srv_test.h | 5 +-
src/lib/testutils/testdata/rwtest.sqlite3 | Bin 16384 -> 17408 bytes
src/lib/util/io/sockaddr_util.h | 1 +
src/lib/util/io/socketsession.h | 53 +-
src/lib/util/locks.h | 2 +-
src/lib/util/tests/Makefile.am | 4 +-
src/lib/util/unittests/Makefile.am | 3 +
src/lib/util/unittests/mock_socketsession.h | 154 +++++
src/lib/xfr/xfrout_client.cc | 10 +-
tests/lettuce/features/bindctl_commands.feature | 2 +-
tests/tools/perfdhcp/Makefile.am | 11 +-
tests/tools/perfdhcp/command_options.cc | 681 ++++++++++++++++++++
tests/tools/perfdhcp/command_options.h | 412 ++++++++++++
.../{badpacket => perfdhcp}/tests/Makefile.am | 5 +-
.../perfdhcp/tests/command_options_unittest.cc | 454 +++++++++++++
.../{badpacket => perfdhcp}/tests/run_unittests.cc | 0
121 files changed, 5621 insertions(+), 597 deletions(-)
delete mode 100644 src/bin/dbutil/b10-dbutil.8
create mode 100644 src/bin/dhcp4/tests/dhcp4_test.py
delete mode 100644 src/bin/dhcp6/interfaces.txt
delete mode 100644 src/bin/resolver/b10-resolver.8
create mode 100644 src/lib/dns/tests/testdata/rrset_toWire3
create mode 100644 src/lib/dns/tests/testdata/rrset_toWire4
create mode 100644 src/lib/python/isc/ddns/Makefile.am
copy src/lib/python/isc/{bind10 => ddns}/__init__.py (100%)
create mode 100644 src/lib/python/isc/ddns/libddns_messages.mes
create mode 100644 src/lib/python/isc/ddns/logger.py
create mode 100644 src/lib/python/isc/ddns/session.py
create mode 100644 src/lib/python/isc/ddns/tests/Makefile.am
create mode 100644 src/lib/python/isc/ddns/tests/session_tests.py
create mode 100644 src/lib/python/isc/ddns/tests/zone_config_tests.py
create mode 100644 src/lib/python/isc/ddns/zone_config.py
create mode 100644 src/lib/python/isc/log_messages/libddns_messages.py
create mode 100644 src/lib/util/unittests/mock_socketsession.h
create mode 100644 tests/tools/perfdhcp/command_options.cc
create mode 100644 tests/tools/perfdhcp/command_options.h
copy tests/tools/{badpacket => perfdhcp}/tests/Makefile.am (74%)
create mode 100644 tests/tools/perfdhcp/tests/command_options_unittest.cc
copy tests/tools/{badpacket => perfdhcp}/tests/run_unittests.cc (100%)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 2e91069..c849b55 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+442. [func] tomek
+ b10-dhcp4, b10-dhcp6: Both DHCP servers now accept -p parameter
+ that can be used to specify listening port number. This capability
+ is useful only for testing purposes.
+ (Trac #1503, git e60af9fa16a6094d2204f27c40a648fae313bdae)
+
+441. [func] tomek
+ libdhcp++: Stub interface detection (support for interfaces.txt
+ file) was removed.
+ (Trac #1281, git 900fc8b420789a8c636bcf20fdaffc60bc1041e0)
+
bind10-devel-20120517 released on May 17. 2012
440. [func] muks
diff --git a/configure.ac b/configure.ac
index 0d6507f..6935431 100644
--- a/configure.ac
+++ b/configure.ac
@@ -966,8 +966,8 @@ AC_SUBST(PERL)
AC_PATH_PROGS(AWK, gawk awk)
AC_SUBST(AWK)
-AC_ARG_ENABLE(man, [AC_HELP_STRING([--disable-man],
- [don't generate documentation])], enable_man=$enableval, enable_man=yes)
+AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man],
+ [regenerate man pages [default=no]])], enable_man=$enableval, enable_man=no)
AM_CONDITIONAL(ENABLE_MAN, test x$enable_man != xno)
@@ -1067,6 +1067,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/testutils/Makefile
src/lib/python/isc/bind10/Makefile
src/lib/python/isc/bind10/tests/Makefile
+ src/lib/python/isc/ddns/Makefile
+ src/lib/python/isc/ddns/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
src/lib/python/isc/server_common/Makefile
@@ -1120,6 +1122,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
+ tests/tools/perfdhcp/tests/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
index c46ae0d..a9903ff 100644
--- a/doc/guide/Makefile.am
+++ b/doc/guide/Makefile.am
@@ -4,7 +4,6 @@ if ENABLE_MAN
# generated documentation
DOCS = bind10-messages.html bind10-guide.html bind10-guide.txt
-docdir = $(pkgdatadir)/doc/$(PACKAGE)
doc_DATA = $(DOCS) bind10-guide.css
# TODO: okay to include the generated bind10-messages.xml in dist tarfile too?
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 27daff7..7839ea7 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1352,6 +1352,24 @@ This may be a temporary setting until then.
and <varname>port</varname> number.
By default, <command>b10-auth</command> listens on port 53
on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+ <note>
+ <simpara>
+ The default configuration is currently not appropriate for a multi-homed host.
+ In case you have multiple public IP addresses, it is possible the
+ query UDP packet comes through one interface and the answer goes out
+ through another. The answer will probably be dropped by the client, as it
+ has a different source address than the one it sent the query to. The
+ client would fallback on TCP after several attempts, which works
+ well in this situation, but is clearly not ideal.
+ </simpara>
+ <simpara>
+ There are plans to solve the problem such that the server handles
+ it by itself. But until it is actually implemented, it is recommended to
+ alter the configuration — remove the wildcard addresses and list all
+ addresses explicitly. Then the server will answer on the same
+ interface the request came on, preserving the correct address.
+ </simpara>
+ </note>
</simpara>
</listitem>
</varlistentry>
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index 257d8eb..34b5155 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -62,6 +62,7 @@ EXTRA_DIST += auth_messages.mes
b10_auth_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
b10_auth_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
b10_auth_LDADD += $(top_builddir)/src/lib/util/libutil.la
+b10_auth_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
b10_auth_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
b10_auth_LDADD += $(top_builddir)/src/lib/cc/libcc.la
b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index b18feb1..aeeb94f 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -255,6 +255,20 @@ 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.
+% AUTH_MESSAGE_FORWARD_ERROR failed to forward %1 request from %2: %3
+The authoritative server tried to forward some type DNS request
+message to a separate process (e.g., forwarding dynamic update
+requests to b10-ddns) to handle it, but it failed. The authoritative
+server returns SERVFAIL to the client on behalf of the separate
+process. The error could be configuration mismatch between b10-auth
+and the recipient component, or it may be because the requests are
+coming too fast and the receipient process cannot keep up with the
+rate, or some system level failure. In either case this means the
+BIND 10 system is not working as expected, so the administrator should
+look into the cause and address the issue. The log message includes
+the client's address (and port), and the error message sent from the
+lower layer that detects the failure.
+
% AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
This is a debug message indicating that the authoritative server has
created a channel to the XFRIN (Transfer-in) process. It is issued
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 50eb931..2bf43ff 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -14,18 +14,10 @@
#include <config.h>
-#include <sys/types.h>
-#include <netinet/in.h>
-
-#include <algorithm>
-#include <cassert>
-#include <iostream>
-#include <vector>
-#include <memory>
-
-#include <boost/bind.hpp>
+#include <util/io/socketsession.h>
#include <asiolink/asiolink.h>
+#include <asiolink/io_endpoint.h>
#include <config/ccsession.h>
@@ -64,6 +56,18 @@
#include <auth/statistics.h>
#include <auth/auth_log.h>
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+#include <vector>
+#include <memory>
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
using namespace std;
using namespace isc;
@@ -71,6 +75,7 @@ using namespace isc::cc;
using namespace isc::datasrc;
using namespace isc::dns;
using namespace isc::util;
+using namespace isc::util::io;
using namespace isc::auth;
using namespace isc::dns::rdata;
using namespace isc::data;
@@ -107,6 +112,107 @@ public:
private:
MessageRenderer& renderer_;
};
+
+// A helper container of socket session forwarder.
+//
+// This class provides a simple wrapper interface to SocketSessionForwarder
+// so that the caller doesn't have to worry about connection management,
+// exception handling or parameter building.
+//
+// It internally maintains whether the underlying forwarder establishes a
+// connection to the receiver. On a forwarding request, if the connection
+// hasn't been established yet, it automatically opens a new one, then
+// pushes the session over it. It also closes the connection on destruction,
+// or a non-recoverable error happens, automatically. So the only thing
+// the application has to do is to create this object and push any session
+// to be forwarded.
+class SocketSessionForwarderHolder {
+public:
+ /// \brief The constructor.
+ ///
+ /// \param message_name Any string that can identify the type of messages
+ /// to be forwarded via this session. It will be only used as part of
+ /// log message, so it can be anything, but in practice something like
+ /// "update" or "xfr" is expected.
+ /// \param forwarder The underlying socket session forwarder.
+ SocketSessionForwarderHolder(const string& message_name,
+ BaseSocketSessionForwarder& forwarder) :
+ message_name_(message_name), forwarder_(forwarder), connected_(false)
+ {}
+
+ ~SocketSessionForwarderHolder() {
+ if (connected_) {
+ forwarder_.close();
+ }
+ }
+
+ /// \brief Push a socket session corresponding to given IOMessage.
+ ///
+ /// If the connection with the receiver process hasn't been established,
+ /// it automatically establishes one, then push the session over it.
+ ///
+ /// If either connect or push fails, the underlying forwarder object should
+ /// throw an exception. This method logs the event, and propagates the
+ /// exception to the caller, which will eventually result in SERVFAIL.
+ /// The connection, if established, is automatically closed, so the next
+ /// forward request will trigger reopening a new connection.
+ ///
+ /// \note: Right now, there's no API to retrieve the local address from
+ /// the IOMessage. Until it's added, we pass the remote address as
+ /// local.
+ ///
+ /// \param io_message The request message to be forwarded as a socket
+ /// session. It will be converted to the parameters that the underlying
+ /// SocketSessionForwarder expects.
+ void push(const IOMessage& io_message) {
+ const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
+ const int protocol = remote_ep.getProtocol();
+ const int sock_type = getSocketType(protocol);
+ try {
+ connect();
+ forwarder_.push(io_message.getSocket().getNative(),
+ remote_ep.getFamily(), sock_type, protocol,
+ remote_ep.getSockAddr(), remote_ep.getSockAddr(),
+ io_message.getData(), io_message.getDataSize());
+ } catch (const SocketSessionError& ex) {
+ LOG_ERROR(auth_logger, AUTH_MESSAGE_FORWARD_ERROR).
+ arg(message_name_).arg(remote_ep).arg(ex.what());
+ close();
+ throw;
+ }
+ }
+
+private:
+ const string message_name_;
+ BaseSocketSessionForwarder& forwarder_;
+ bool connected_;
+
+ void connect() {
+ if (!connected_) {
+ forwarder_.connectToReceiver();
+ connected_ = true;
+ }
+ }
+
+ void close() {
+ if (connected_) {
+ forwarder_.close();
+ connected_ = false;
+ }
+ }
+
+ static int getSocketType(int protocol) {
+ switch (protocol) {
+ case IPPROTO_UDP:
+ return (SOCK_DGRAM);
+ case IPPROTO_TCP:
+ return (SOCK_STREAM);
+ default:
+ isc_throw(isc::InvalidParameter,
+ "Unexpected socket address family: " << protocol);
+ }
+ }
+};
}
class AuthSrvImpl {
@@ -115,7 +221,8 @@ private:
AuthSrvImpl(const AuthSrvImpl& source);
AuthSrvImpl& operator=(const AuthSrvImpl& source);
public:
- AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client);
+ AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client,
+ BaseSocketSessionForwarder& ddns_forwarder);
~AuthSrvImpl();
isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
@@ -128,6 +235,7 @@ public:
bool processNotify(const IOMessage& io_message, Message& message,
OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context);
+ bool processUpdate(const IOMessage& io_message);
IOService io_service_;
@@ -189,6 +297,9 @@ private:
bool xfrout_connected_;
AbstractXfroutClient& xfrout_client_;
+ // Socket session forwarder for dynamic update requests
+ SocketSessionForwarderHolder ddns_forwarder_;
+
/// Increment query counter
void incCounter(const int protocol);
@@ -199,7 +310,8 @@ private:
};
AuthSrvImpl::AuthSrvImpl(const bool use_cache,
- AbstractXfroutClient& xfrout_client) :
+ AbstractXfroutClient& xfrout_client,
+ BaseSocketSessionForwarder& ddns_forwarder) :
config_session_(NULL),
xfrin_session_(NULL),
memory_client_class_(RRClass::IN()),
@@ -207,7 +319,8 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
counters_(),
keyring_(NULL),
xfrout_connected_(false),
- xfrout_client_(xfrout_client)
+ xfrout_client_(xfrout_client),
+ ddns_forwarder_("update", ddns_forwarder)
{
// cur_datasrc_ is automatically initialized by the default constructor,
// effectively being an empty (sqlite) data source. once ccsession is up
@@ -277,9 +390,10 @@ private:
AuthSrv* server_;
};
-AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client)
+AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client,
+ BaseSocketSessionForwarder& ddns_forwarder)
{
- impl_ = new AuthSrvImpl(use_cache, xfrout_client);
+ impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
checkin_ = new ConfigChecker(this);
dns_lookup_ = new MessageLookup(this);
dns_answer_ = new MessageAnswer(this);
@@ -527,16 +641,19 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
return;
}
+ const Opcode opcode = message.getOpcode();
bool send_answer = true;
try {
// update per opcode statistics counter. This can only be reliable
// after TSIG check succeeds.
impl_->counters_.inc(message.getOpcode());
- if (message.getOpcode() == Opcode::NOTIFY()) {
+ if (opcode == Opcode::NOTIFY()) {
send_answer = impl_->processNotify(io_message, message, buffer,
tsig_context);
- } else if (message.getOpcode() != Opcode::QUERY()) {
+ } else if (opcode == Opcode::UPDATE()) {
+ send_answer = impl_->processUpdate(io_message);
+ } else if (opcode != Opcode::QUERY()) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
.arg(message.getOpcode().toText());
makeErrorMessage(impl_->renderer_, message, buffer,
@@ -546,7 +663,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
Rcode::FORMERR(), tsig_context);
} else {
ConstQuestionPtr question = *message.beginQuestion();
- const RRType &qtype = question->getType();
+ const RRType& qtype = question->getType();
if (qtype == RRType::AXFR()) {
send_answer = impl_->processXfrQuery(io_message, message,
buffer, tsig_context);
@@ -754,6 +871,15 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
return (true);
}
+bool
+AuthSrvImpl::processUpdate(const IOMessage& io_message) {
+ // Push the update request to a separate process via the forwarder.
+ // On successful push, the request shouldn't be responded from b10-auth,
+ // so we return false.
+ ddns_forwarder_.push(io_message);
+ return (false);
+}
+
void
AuthSrvImpl::incCounter(const int protocol) {
// Increment query counter.
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index d74a42f..18d7503 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -37,6 +37,14 @@
#include <auth/statistics.h>
namespace isc {
+namespace util {
+namespace io {
+class BaseSocketSessionForwarder;
+}
+}
+namespace datasrc {
+class InMemoryClient;
+}
namespace xfr {
class AbstractXfroutClient;
}
@@ -90,7 +98,8 @@ public:
/// but can refer to a local mock object for testing (or other
/// experimental) purposes.
AuthSrv(const bool use_cache,
- isc::xfr::AbstractXfroutClient& xfrout_client);
+ isc::xfr::AbstractXfroutClient& xfrout_client,
+ isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
~AuthSrv();
//@}
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index aa238c0..2e705e4 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.cc
@@ -31,9 +31,10 @@
#include <dns/rrclass.h>
#include <log/logger_support.h>
-
#include <xfr/xfrout_client.h>
+#include <util/unittests/mock_socketsession.h>
+
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/query.h>
@@ -48,6 +49,7 @@ using namespace isc::auth;
using namespace isc::dns;
using namespace isc::log;
using namespace isc::util;
+using namespace isc::util::unittests;
using namespace isc::xfr;
using namespace isc::bench;
using namespace isc::asiodns;
@@ -78,7 +80,7 @@ protected:
QueryBenchMark(const bool enable_cache,
const BenchQueries& queries, Message& query_message,
OutputBuffer& buffer) :
- server_(new AuthSrv(enable_cache, xfrout_client)),
+ server_(new AuthSrv(enable_cache, xfrout_client, ddns_forwarder)),
queries_(queries),
query_message_(query_message),
buffer_(buffer),
@@ -103,6 +105,8 @@ public:
return (queries_.size());
}
+private:
+ MockSocketSessionForwarder ddns_forwarder;
protected:
AuthSrvPtr server_;
private:
diff --git a/src/bin/auth/common.cc b/src/bin/auth/common.cc
index 1602a1a..2c21895 100644
--- a/src/bin/auth/common.cc
+++ b/src/bin/auth/common.cc
@@ -33,7 +33,25 @@ getXfroutSocketPath() {
if (getenv("BIND10_XFROUT_SOCKET_FILE") != NULL) {
return (getenv("BIND10_XFROUT_SOCKET_FILE"));
} else {
- return (UNIX_SOCKET_FILE);
+ return (UNIX_XFROUT_SOCKET_FILE);
+ }
+ }
+}
+
+string
+getDDNSSocketPath() {
+ if (getenv("B10_FROM_BUILD") != NULL) {
+ if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR") != NULL) {
+ return (string(getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) +
+ "/ddns_socket");
+ } else {
+ return (string(getenv("B10_FROM_BUILD")) + "/ddns_socket");
+ }
+ } else {
+ if (getenv("BIND10_DDNS_SOCKET_FILE") != NULL) {
+ return (getenv("BIND10_DDNS_SOCKET_FILE"));
+ } else {
+ return (UNIX_DDNS_SOCKET_FILE);
}
}
}
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index cf71214..9a1942c 100644
--- a/src/bin/auth/common.h
+++ b/src/bin/auth/common.h
@@ -38,6 +38,20 @@ public:
/// The logic should be the same as in b10-xfrout, so they find each other.
std::string getXfroutSocketPath();
+/// \brief Get the path of socket to talk to ddns
+///
+/// It takes some environment variables into account (B10_FROM_BUILD,
+/// B10_FROM_SOURCE_LOCALSTATEDIR and BIND10_DDNS_SOCKET_FILE). It
+/// also considers the installation prefix.
+///
+/// The logic should be the same as in b10-ddns, so they find each other.
+///
+/// Note: eventually we should find a better way so that we don't have to
+/// repeat the same magic value (and how to tweak it with some magic
+/// environment variable) twice, at which point this function may be able
+/// to be deprecated.
+std::string getDDNSSocketPath();
+
/// \brief The name used when identifieng the process
///
/// This is currently b10-auth, but it can be changed easily in one place.
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index fc2f751..67d0f3d 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -28,6 +28,7 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
+#include <util/io/socketsession.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
@@ -60,6 +61,7 @@ using namespace isc::data;
using namespace isc::dns;
using namespace isc::log;
using namespace isc::util;
+using namespace isc::util::io;
using namespace isc::xfr;
namespace {
@@ -130,6 +132,7 @@ main(int argc, char* argv[]) {
bool statistics_session_established = false; // XXX (see Trac #287)
ModuleCCSession* config_session = NULL;
XfroutClient xfrout_client(getXfroutSocketPath());
+ SocketSessionForwarder ddns_forwarder(getDDNSSocketPath());
try {
string specfile;
if (getenv("B10_FROM_BUILD")) {
@@ -139,7 +142,7 @@ main(int argc, char* argv[]) {
specfile = string(AUTH_SPECFILE_LOCATION);
}
- auth_server = new AuthSrv(cache, xfrout_client);
+ auth_server = new AuthSrv(cache, xfrout_client, ddns_forwarder);
LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
SimpleCallback* checkin = auth_server->getCheckinProvider();
diff --git a/src/bin/auth/spec_config.h.pre.in b/src/bin/auth/spec_config.h.pre.in
index 1b1df19..586ea7c 100644
--- a/src/bin/auth/spec_config.h.pre.in
+++ b/src/bin/auth/spec_config.h.pre.in
@@ -13,4 +13,5 @@
// PERFORMANCE OF THIS SOFTWARE.
#define AUTH_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/auth.spec"
-#define UNIX_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_XFROUT_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
+#define UNIX_DDNS_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/ddns_socket"
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 3a7c54b..4b6621e 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -54,9 +54,7 @@ nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
+run_unittests_LDADD = $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
@@ -71,6 +69,8 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libstatistics.la
+run_unittests_LDADD += $(GTEST_LDADD)
+run_unittests_LDADD += $(SQLITE_LIBS)
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 17a9312..a5b6d26 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -14,12 +14,7 @@
#include <config.h>
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/scoped_ptr.hpp>
-
-#include <gtest/gtest.h>
+#include <util/io/sockaddr_util.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
@@ -39,6 +34,7 @@
#include <auth/common.h>
#include <auth/statistics.h>
+#include <util/unittests/mock_socketsession.h>
#include <dns/tests/unittest_util.h>
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
@@ -46,10 +42,24 @@
#include <testutils/portconfig.h>
#include <testutils/socket_request.h>
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
using namespace std;
using namespace isc::cc;
using namespace isc::dns;
using namespace isc::util;
+using namespace isc::util::io::internal;
+using namespace isc::util::unittests;
using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::xfr;
@@ -58,6 +68,7 @@ using namespace isc::asiolink;
using namespace isc::testutils;
using namespace isc::server_common::portconfig;
using isc::UnitTestUtil;
+using boost::scoped_ptr;
namespace {
const char* const CONFIG_TESTDB =
@@ -78,7 +89,7 @@ class AuthSrvTest : public SrvTestBase {
protected:
AuthSrvTest() :
dnss_(),
- server(true, xfrout),
+ server(true, xfrout, ddns_forwarder),
rrclass(RRClass::IN()),
// The empty string is expected value of the parameter of
// requestSocket, not the app_name (there's no fallback, it checks
@@ -144,9 +155,30 @@ protected:
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
+ // Convenient shortcut of creating a simple request and having the
+ // server process it.
+ void createAndSendRequest(RRType req_type, Opcode opcode = Opcode::QUERY(),
+ const Name& req_name = Name("example.com"),
+ RRClass req_class = RRClass::IN(),
+ int protocol = IPPROTO_UDP,
+ const char* const remote_address =
+ DEFAULT_REMOTE_ADDRESS,
+ uint16_t remote_port = DEFAULT_REMOTE_PORT)
+ {
+ UnitTestUtil::createRequestMessage(request_message, opcode,
+ default_qid, req_name,
+ req_class, req_type);
+ createRequestPacket(request_message, protocol, NULL,
+ remote_address, remote_port);
+ parse_message->clear(Message::PARSE);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ }
+
MockDNSService dnss_;
MockSession statistics_session;
MockXfroutClient xfrout;
+ MockSocketSessionForwarder ddns_forwarder;
AuthSrv server;
const RRClass rrclass;
vector<uint8_t> response_data;
@@ -254,8 +286,8 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
unsupportedRequest();
- // unsupportedRequest tries 14 different opcodes
- checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 14);
+ // unsupportedRequest tries 13 different opcodes
+ checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 13);
}
// Multiple questions. Should result in FORMERR.
@@ -1488,4 +1520,128 @@ TEST_F(AuthSrvTest,
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
+//
+// DDNS related tests
+//
+
+// Helper subroutine to check if the given socket address has the expected
+// address and port. It depends on specific output of getnameinfo() (while
+// there can be multiple textual representation of the same address) but
+// in practice it should be reliable.
+void
+checkAddrPort(const struct sockaddr& actual_sa,
+ const string& expected_addr, uint16_t expected_port)
+{
+ char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
+ sizeof(hbuf), sbuf, sizeof(sbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (error != 0) {
+ isc_throw(isc::Unexpected, "getnameinfo failed: " <<
+ gai_strerror(error));
+ }
+ EXPECT_EQ(expected_addr, hbuf);
+ EXPECT_EQ(boost::lexical_cast<string>(expected_port), sbuf);
+}
+
+TEST_F(AuthSrvTest, DDNSForward) {
+ EXPECT_FALSE(ddns_forwarder.isConnected());
+
+ // Repeat sending an update request 4 times, differing some network
+ // parameters: UDP/IPv4, TCP/IPv4, UDP/IPv6, TCP/IPv6, in this order.
+ // By doing that we can also confirm the forwarder connection will be
+ // established exactly once, and kept established.
+ for (size_t i = 0; i < 4; ++i) {
+ // Use different names for some different cases
+ const Name zone_name = Name(i < 2 ? "example.com" : "example.org");
+ const socklen_t family = (i < 2) ? AF_INET : AF_INET6;
+ const char* const remote_addr =
+ (family == AF_INET) ? "192.0.2.1" : "2001:db8::1";
+ const uint16_t remote_port =
+ (family == AF_INET) ? 53214 : 53216;
+ const int protocol = ((i % 2) == 0) ? IPPROTO_UDP : IPPROTO_TCP;
+
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), zone_name,
+ RRClass::IN(), protocol, remote_addr,
+ remote_port);
+ EXPECT_FALSE(dnsserv.hasAnswer());
+ EXPECT_TRUE(ddns_forwarder.isConnected());
+
+ // Examine the pushed data (note: currently "local end" has a dummy
+ // value equal to remote)
+ EXPECT_EQ(family, ddns_forwarder.getPushedFamily());
+ const int expected_type =
+ (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+ EXPECT_EQ(expected_type, ddns_forwarder.getPushedType());
+ EXPECT_EQ(protocol, ddns_forwarder.getPushedProtocol());
+ checkAddrPort(ddns_forwarder.getPushedRemoteend(),
+ remote_addr, remote_port);
+ checkAddrPort(ddns_forwarder.getPushedLocalend(),
+ remote_addr, remote_port);
+ EXPECT_EQ(io_message->getDataSize(),
+ ddns_forwarder.getPushedData().size());
+ EXPECT_EQ(0, memcmp(io_message->getData(),
+ &ddns_forwarder.getPushedData()[0],
+ ddns_forwarder.getPushedData().size()));
+ }
+}
+
+TEST_F(AuthSrvTest, DDNSForwardConnectFail) {
+ // make connect attempt fail. It should result in SERVFAIL. Note that
+ // the question (zone) section should be cleared for opcode of update.
+ ddns_forwarder.disableConnect();
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+ EXPECT_FALSE(ddns_forwarder.isConnected());
+
+ // Now make connect okay again. Despite the previous failure the new
+ // connection should now be established.
+ ddns_forwarder.enableConnect();
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+ EXPECT_FALSE(dnsserv.hasAnswer());
+ EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardPushFail) {
+ // Make first request succeed, which will establish the connection.
+ EXPECT_FALSE(ddns_forwarder.isConnected());
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+ EXPECT_TRUE(ddns_forwarder.isConnected());
+
+ // make connect attempt fail. It should result in SERVFAIL. The
+ // connection should be closed. Use IPv6 address for varying log output.
+ ddns_forwarder.disablePush();
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), Name("example.com"),
+ RRClass::IN(), IPPROTO_UDP, "2001:db8::2");
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
+ EXPECT_FALSE(ddns_forwarder.isConnected());
+
+ // Allow push again. Connection will be reopened, and the request will
+ // be forwarded successfully.
+ ddns_forwarder.enablePush();
+ createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
+ EXPECT_FALSE(dnsserv.hasAnswer());
+ EXPECT_TRUE(ddns_forwarder.isConnected());
+}
+
+TEST_F(AuthSrvTest, DDNSForwardClose) {
+ scoped_ptr<AuthSrv> tmp_server(new AuthSrv(true, xfrout, ddns_forwarder));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::UPDATE(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ tmp_server->processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ EXPECT_FALSE(dnsserv.hasAnswer());
+ EXPECT_TRUE(ddns_forwarder.isConnected());
+
+ // Destroy the server. The forwarder should close the connection.
+ tmp_server.reset();
+ EXPECT_FALSE(ddns_forwarder.isConnected());
+}
+
}
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index f134d40..ec00d11 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -33,6 +33,7 @@
#include <asiolink/asiolink.h>
+#include <util/unittests/mock_socketsession.h>
#include <testutils/mockups.h>
#include <cassert>
@@ -52,6 +53,7 @@ using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::config;
+using namespace isc::util::unittests;
using namespace isc::testutils;
using namespace isc::auth::unittest;
@@ -60,7 +62,7 @@ namespace {
class AuthCommandTest : public ::testing::Test {
protected:
AuthCommandTest() :
- server_(false, xfrout_),
+ server_(false, xfrout_, ddns_forwarder_),
rcode_(-1),
expect_rcode_(0),
itimer_(server_.getIOService())
@@ -73,6 +75,7 @@ protected:
}
MockSession statistics_session_;
MockXfroutClient xfrout_;
+ MockSocketSessionForwarder ddns_forwarder_;
AuthSrv server_;
ConstElementPtr result_;
// The shutdown command parameter
diff --git a/src/bin/auth/tests/common_unittest.cc b/src/bin/auth/tests/common_unittest.cc
index 184988d..b2d072a 100644
--- a/src/bin/auth/tests/common_unittest.cc
+++ b/src/bin/auth/tests/common_unittest.cc
@@ -60,37 +60,63 @@ protected:
EXPECT_EQ(0, setenv(name.c_str(), value.c_str(), 1));
}
}
- // Test getXfroutSocketPath under given environment
- void testXfrout(const string& fromBuild, const string& localStateDir,
- const string& socketFile, const string& expected)
+ // Test getter functions for a socket file path under given environment
+ void testSocketPath(const string& fromBuild, const string& localStateDir,
+ const string& socketFile, const string& env_name,
+ const string& expected, string (*actual_fn)())
{
setEnv("B10_FROM_BUILD", fromBuild);
setEnv("B10_FROM_SOURCE_LOCALSTATEDIR", localStateDir);
- setEnv("BIND10_XFROUT_SOCKET_FILE", socketFile);
- EXPECT_EQ(expected, getXfroutSocketPath());
+ setEnv(env_name, socketFile);
+ EXPECT_EQ(expected, actual_fn());
}
};
// Test that when we have no special environment, we get the default from prefix
TEST_F(Paths, xfroutNoEnv) {
- testXfrout("", "", "", UNIX_SOCKET_FILE);
+ testSocketPath("", "", "", "BIND10_XFROUT_SOCKET_FILE",
+ UNIX_XFROUT_SOCKET_FILE, getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsNoEnv) {
+ testSocketPath("", "", "", "BIND10_DDNS_SOCKET_FILE",
+ UNIX_DDNS_SOCKET_FILE, getDDNSSocketPath);
}
// Override by B10_FROM_BUILD
TEST_F(Paths, xfroutFromBuild) {
- testXfrout("/from/build", "", "/wrong/path",
- "/from/build/auth_xfrout_conn");
+ testSocketPath("/from/build", "", "/wrong/path",
+ "BIND10_XFROUT_SOCKET_FILE", "/from/build/auth_xfrout_conn",
+ getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromBuild) {
+ testSocketPath("/from/build", "", "/wrong/path", "BIND10_DDNS_SOCKET_FILE",
+ "/from/build/ddns_socket", getDDNSSocketPath);
}
// Override by B10_FROM_SOURCE_LOCALSTATEDIR
TEST_F(Paths, xfroutLocalStatedir) {
- testXfrout("/wrong/path", "/state/dir", "/wrong/path",
- "/state/dir/auth_xfrout_conn");
+ testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+ "BIND10_XFROUT_SOCKET_FILE", "/state/dir/auth_xfrout_conn",
+ getXfroutSocketPath);
}
-// Override by BIND10_XFROUT_SOCKET_FILE explicitly
+TEST_F(Paths, ddnsLocalStatedir) {
+ testSocketPath("/wrong/path", "/state/dir", "/wrong/path",
+ "BIND10_DDNS_SOCKET_FILE", "/state/dir/ddns_socket",
+ getDDNSSocketPath);
+}
+
+// Override by BIND10_xxx_SOCKET_FILE explicitly
TEST_F(Paths, xfroutFromEnv) {
- testXfrout("", "", "/the/path/to/file", "/the/path/to/file");
+ testSocketPath("", "", "/the/path/to/file", "BIND10_XFROUT_SOCKET_FILE",
+ "/the/path/to/file", getXfroutSocketPath);
+}
+
+TEST_F(Paths, ddnsFromEnv) {
+ testSocketPath("", "", "/the/path/to/file", "BIND10_DDNS_SOCKET_FILE",
+ "/the/path/to/file", getDDNSSocketPath);
}
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 3ff9324..e2d193a 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -32,6 +32,7 @@
#include "datasrc_util.h"
+#include <util/unittests/mock_socketsession.h>
#include <testutils/mockups.h>
#include <testutils/portconfig.h>
#include <testutils/socket_request.h>
@@ -44,6 +45,7 @@ using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::asiodns;
using namespace isc::auth::unittest;
+using namespace isc::util::unittests;
using namespace isc::testutils;
namespace {
@@ -52,7 +54,7 @@ protected:
AuthConfigTest() :
dnss_(),
rrclass(RRClass::IN()),
- server(true, xfrout),
+ server(true, xfrout, ddns_forwarder),
// The empty string is expected value of the parameter of
// requestSocket, not the app_name (there's no fallback, it checks
// the empty string is passed).
@@ -63,6 +65,7 @@ protected:
MockDNSService dnss_;
const RRClass rrclass;
MockXfroutClient xfrout;
+ MockSocketSessionForwarder ddns_forwarder;
AuthSrv server;
isc::server_common::portconfig::AddressList address_store_;
private:
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 60449de..4053783 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -116,7 +116,7 @@
<refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
with the default configuration.
The name of the backup file can be found in the logs
- (<varname>CFGMGR_RENAMED_CONFIG_FILE</varname>).
+ (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
(It will append a number to the backup filename if a
previous backup file exists.)
diff --git a/src/bin/bindctl/command_sets.py b/src/bin/bindctl/command_sets.py
index 9e2c2ef..c001ec8 100644
--- a/src/bin/bindctl/command_sets.py
+++ b/src/bin/bindctl/command_sets.py
@@ -92,4 +92,3 @@ def prepare_execute_commands(tool):
module.add_command(cmd)
tool.add_module_info(module)
-
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index f8d590b..202ea09 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -107,7 +107,7 @@
<refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
will use the default configurations.
The name of the backup file can be found in the logs
- (<varname>CFGMGR_RENAMED_CONFIG_FILE</varname>).
+ (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
(It will append a number to the backup filename if a
previous backup file exists.)
</para>
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 66d8f2a..351e8bf 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -209,7 +209,6 @@ class TestParseArgs(unittest.TestCase):
self.assertFalse(parsed.clear_config)
parsed = b.parse_options(['--clear-config'], TestOptParser)
self.assertTrue(parsed.clear_config)
-
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8
deleted file mode 100644
index 437a69d..0000000
--- a/src/bin/dbutil/b10-dbutil.8
+++ /dev/null
@@ -1,92 +0,0 @@
-'\" t
-.\" Title: b10-dbutil
-.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: March 20, 2012
-.\" Manual: BIND10
-.\" Source: BIND10
-.\" Language: English
-.\"
-.TH "B10\-DBUTIL" "8" "March 20, 2012" "BIND10" "BIND10"
-.\" -----------------------------------------------------------------
-.\" * Define some portability stuff
-.\" -----------------------------------------------------------------
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.\" http://bugs.debian.org/507673
-.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.ie \n(.g .ds Aq \(aq
-.el .ds Aq '
-.\" -----------------------------------------------------------------
-.\" * set default formatting
-.\" -----------------------------------------------------------------
-.\" disable hyphenation
-.nh
-.\" disable justification (adjust text to left margin only)
-.ad l
-.\" -----------------------------------------------------------------
-.\" * MAIN CONTENT STARTS HERE *
-.\" -----------------------------------------------------------------
-.SH "NAME"
-b10-dbutil \- Zone Database Maintenance Utility
-.SH "SYNOPSIS"
-.HP \w'\fBb10\-dbutil\ \-\-check\fR\ 'u
-\fBb10\-dbutil \-\-check\fR [\-\-verbose] [\-\-quiet] [\fIdbfile\fR]
-.HP \w'\fBb10\-dbutil\ \-\-upgrade\fR\ 'u
-\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\-\-quiet] [\fIdbfile\fR]
-.SH "DESCRIPTION"
-.PP
-The
-\fBb10\-dbutil\fR
-utility is a general administration utility for SQL databases\&. (Currently only SQLite is supported by BIND 10\&.) It can report the current verion of the schema, and upgrade an existing database to the latest version of the schema\&.
-.PP
-
-\fBb10\-dbutil\fR
-operates in one of two modes, check mode or upgrade mode\&.
-.PP
-In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates an error during command\-line parsing or execution\&.
-.PP
-When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on)\&. Exit status is 0 if the upgrade is either succesful or aborted by the user, and non\-zero if there is an error\&.
-.PP
-When upgrading the database, it is
-\fIstrongly\fR
-recommended that BIND 10 not be running while the upgrade is in progress\&.
-.SH "ARGUMENTS"
-.PP
-The arguments are as follows:
-.PP
-\fB\-\-check\fR
-.RS 4
-Selects the version check function, which reports the current version of the database\&. This is incompatible with the \-\-upgrade option\&.
-.RE
-.PP
-\fB\-\-noconfirm\fR
-.RS 4
-Only valid with \-\-upgrade, this disables the prompt\&. Normally the utility will print a warning that an upgrade is about to take place and request that you type "Yes" to continue\&. If this switch is given on the command line, no prompt will be issued: the utility will just perform the upgrade\&.
-.RE
-.PP
-\fB\-\-upgrade\fR
-.RS 4
-Selects the upgrade function, which upgrades the database to the latest version of the schema\&. This is incompatible with the \-\-upgrade option\&.
-.sp
-The upgrade function will upgrade a BIND 10 database \- no matter how old the schema \- preserving all data\&. A backup file is created before the upgrade (with the same name as the database, but with "\&.backup" suffixed to it)\&. If the upgrade fails, this file can be copied back to restore the original database\&.
-.RE
-.PP
-\fB\-\-verbose\fR
-.RS 4
-Enable verbose mode\&. Each SQL command issued by the utility will be printed to stderr before it is executed\&.
-.RE
-.PP
-\fB\-\-quiet\fR
-.RS 4
-Enable quiet mode\&. No output is printed, except errors during command\-line argument parsing, or the user confirmation dialog\&.
-.RE
-.PP
-\fB\fIdbfile\fR\fR
-.RS 4
-Name of the database file to check of upgrade\&.
-.RE
-.SH "COPYRIGHT"
-.br
-Copyright \(co 2012 Internet Systems Consortium, Inc. ("ISC")
-.br
diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in
index 81f351e..4b76a56 100755
--- a/src/bin/dbutil/dbutil.py.in
+++ b/src/bin/dbutil/dbutil.py.in
@@ -196,7 +196,7 @@ UPGRADES = [
}
# To extend this, leave the above statements in place and add another
-# dictionary to the list. The "from" version should be (2, 0), the "to"
+# dictionary to the list. The "from" version should be (2, 0), the "to"
# version whatever the version the update is to, and the SQL statements are
# the statements required to perform the upgrade. This way, the upgrade
# program will be able to upgrade both a V1.0 and a V2.0 database.
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index 6c52c05..c828fdc 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -18,10 +18,10 @@ man_MANS = b10-dhcp4.8
EXTRA_DIST = $(man_MANS) b10-dhcp4.xml dhcp4.spec
if ENABLE_MAN
-
b10-dhcp4.8: b10-dhcp4.xml
- xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dhcp4.xml
-
+ xsltproc --novalid --xinclude --nonet -o $@ \
+ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+ $(srcdir)/b10-dhcp4.xml
endif
spec_config.h: spec_config.h.pre
@@ -37,7 +37,5 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/liblog.la
-# TODO: config.h.in is wrong because doesn't honor pkgdatadir
-# and can't use @datadir@ because doesn't expand default ${prefix}
b10_dhcp4dir = $(pkgdatadir)
b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc
index ee40295..f3fd8ad 100644
--- a/src/bin/dhcp4/main.cc
+++ b/src/bin/dhcp4/main.cc
@@ -37,6 +37,7 @@
#include <dhcp4/spec_config.h>
#include <dhcp4/dhcp4_srv.h>
+#include <dhcp/dhcp4.h>
using namespace std;
using namespace isc::util;
@@ -53,33 +54,45 @@ usage() {
cerr << "Usage: b10-dhcp4 [-v]"
<< endl;
cerr << "\t-v: verbose output" << endl;
- exit(1);
+ cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+ exit(EXIT_FAILURE);
}
} // end of anonymous namespace
int
main(int argc, char* argv[]) {
int ch;
+ int port_number = DHCP4_SERVER_PORT; // The default. any other values are
+ // useful for testing only.
- while ((ch = getopt(argc, argv, ":v")) != -1) {
+ while ((ch = getopt(argc, argv, "vp:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
isc::log::denabled = true;
break;
+ case 'p':
+ port_number = strtol(optarg, NULL, 10);
+ if (port_number == 0) {
+ cerr << "Failed to parse port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ break;
case ':':
default:
usage();
}
}
- cout << "My pid=" << getpid() << endl;
+ cout << "My pid=" << getpid() << ", binding to port " << port_number
+ << ", verbose " << (verbose_mode?"yes":"no") << endl;
if (argc - optind > 0) {
usage();
}
- int ret = 0;
+ int ret = EXIT_SUCCESS;
// TODO remainder of auth to dhcp4 code copy. We need to enable this in
// dhcp4 eventually
@@ -99,13 +112,13 @@ main(int argc, char* argv[]) {
cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
- Dhcpv4Srv* srv = new Dhcpv4Srv();
+ Dhcpv4Srv* srv = new Dhcpv4Srv(port_number);
srv->run();
} catch (const std::exception& ex) {
cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
- ret = 1;
+ ret = EXIT_FAILURE;
}
return (ret);
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index b956aee..a327e47 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -1,12 +1,25 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
+PYTESTS = dhcp4_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# Explicitly specify paths to dynamic libraries required by loadable python
+# modules. That is required on Mac OS systems. Otherwise we will get exception
+# about python not being able to load liblog library.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
AM_CPPFLAGS += -I$(top_srcdir)/src/bin
diff --git a/src/bin/dhcp4/tests/dhcp4_test.py b/src/bin/dhcp4/tests/dhcp4_test.py
new file mode 100644
index 0000000..18d23ff
--- /dev/null
+++ b/src/bin/dhcp4/tests/dhcp4_test.py
@@ -0,0 +1,170 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+from bind10_src import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+
+import unittest
+import sys
+import os
+import signal
+import socket
+from isc.net.addr import IPAddr
+import time
+import isc
+import fcntl
+
+class TestDhcpv4Daemon(unittest.TestCase):
+ def setUp(self):
+ # don't redirect stdout/stderr here as we want to print out things
+ # during the test
+ pass
+
+ def tearDown(self):
+ pass
+
+ def runDhcp4(self, params, wait=1):
+ """
+ This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
+ """
+ ## @todo: Convert this into generic method and reuse it in dhcp6
+
+ print("Running command: %s" % (" ".join(params)))
+
+ # redirect stdout to a pipe so we can check that our
+ # process spawning is doing the right thing with stdout
+ self.stdout_old = os.dup(sys.stdout.fileno())
+ self.stdout_pipes = os.pipe()
+ os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
+ os.close(self.stdout_pipes[1])
+
+ # do the same trick for stderr:
+ self.stderr_old = os.dup(sys.stderr.fileno())
+ self.stderr_pipes = os.pipe()
+ os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
+ os.close(self.stderr_pipes[1])
+
+ # note that we use dup2() to restore the original stdout
+ # to the main program ASAP in each test... this prevents
+ # hangs reading from the child process (as the pipe is only
+ # open in the child), and also insures nice pretty output
+
+ pi = ProcessInfo('Test Process', params)
+ pi.spawn()
+ time.sleep(wait)
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+ # Set non-blocking read on pipes. Process may not print anything
+ # on specific output and the we would hang without this.
+ fd = self.stdout_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ fd = self.stderr_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ # There's potential problem if b10-dhcp4 prints out more
+ # than 4k of text
+ try:
+ output = os.read(self.stdout_pipes[0], 4096)
+ except OSError:
+ print("No data available from stdout")
+ output = ""
+
+ # read can return None. Make sure we have a string
+ if (output is None):
+ output = ""
+
+ try:
+ error = os.read(self.stderr_pipes[0], 4096)
+ except OSError:
+ print("No data available on stderr")
+ error = ""
+
+ # read can return None. Make sure we have a string
+ if (error is None):
+ error = ""
+
+
+ try:
+ if (not pi.process.poll()):
+ # let's be nice at first...
+ pi.process.terminate()
+ except OSError:
+ print("Ignoring failed kill attempt. Process is dead already.")
+
+ # call this to get returncode, process should be dead by now
+ rc = pi.process.wait()
+
+ # Clean up our stdout/stderr munging.
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.close(self.stdout_pipes[0])
+
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ os.close(self.stderr_pipes[0])
+
+ print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
+ % (rc, len(output), len(error)) )
+
+ return (rc, output, error)
+
+ def test_alive(self):
+ print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
+ print(" not that is can bind sockets correctly. Please ignore binding errors.")
+
+ (returncode, output, error) = self.runDhcp4(["../b10-dhcp4", "-v"])
+
+ self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
+
+ def test_portnumber_0(self):
+ print("Check that specifying port number 0 is not allowed.")
+
+ (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '0'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+ def test_portnumber_missing(self):
+ print("Check that -p option requires a parameter.")
+
+ (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("option requires an argument"), 1)
+
+ def test_portnumber_nonroot(self):
+ print("Check that specifying unprivileged port number will work.")
+
+ (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '10057'])
+
+ # When invalid port number is specified, return code must not be success
+ # TODO: Temporarily commented out as socket binding on systems that do not have
+ # interface detection implemented currently fails.
+ # self.assertTrue(returncode == 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index abfffb9..44b4e9b 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -2,9 +2,8 @@ SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
-AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
- AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -14,16 +13,17 @@ endif
pkglibexecdir = $(libexecdir)/@PACKAGE@
-CLEANFILES = *.gcno *.gcda spec_config.h
+CLEANFILES = spec_config.h
man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) b10-dhcp6.xml dhcp6.spec interfaces.txt
+EXTRA_DIST = $(man_MANS) b10-dhcp6.xml dhcp6.spec
if ENABLE_MAN
b10-dhcp6.8: b10-dhcp6.xml
- xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dhcp6.xml
-
+ xsltproc --novalid --xinclude --nonet -o $@ \
+ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+ $(srcdir)/b10-dhcp6.xml
endif
spec_config.h: spec_config.h.pre
@@ -39,7 +39,5 @@ b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
-# TODO: config.h.in is wrong because doesn't honor pkgdatadir
-# and can't use @datadir@ because doesn't expand default ${prefix}
b10_dhcp6dir = $(pkgdatadir)
-b10_dhcp6_DATA = dhcp6.spec interfaces.txt
+b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 4497e22..293e600 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -40,7 +40,7 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
- cout << "Initialization" << endl;
+ cout << "Initialization: opening sockets on port " << port << endl;
// first call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
diff --git a/src/bin/dhcp6/interfaces.txt b/src/bin/dhcp6/interfaces.txt
deleted file mode 100644
index 6a64309..0000000
--- a/src/bin/dhcp6/interfaces.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-eth0 fe80::21e:8cff:fe9b:7349
-
-#
-# only first line is read.
-# please use following format:
-# interface-name link-local-ipv6-address
-#
-# This file will become obsolete once proper interface detection
-# is implemented.
-#
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index 5323811..62c0d20 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -53,20 +53,31 @@ usage() {
cerr << "Usage: b10-dhcp6 [-v]"
<< endl;
cerr << "\t-v: verbose output" << endl;
- exit(1);
+ cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+ exit(EXIT_FAILURE);
}
} // end of anonymous namespace
int
main(int argc, char* argv[]) {
int ch;
+ int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
+ // useful for testing only.
- while ((ch = getopt(argc, argv, ":v")) != -1) {
+ while ((ch = getopt(argc, argv, "vp:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
isc::log::denabled = true;
break;
+ case 'p':
+ port_number = strtol(optarg, NULL, 10);
+ if (port_number == 0) {
+ cerr << "Failed to parse port number: [" << optarg
+ << "], 1-65535 allowed." << endl;
+ usage();
+ }
+ break;
case ':':
default:
usage();
@@ -79,7 +90,7 @@ main(int argc, char* argv[]) {
usage();
}
- int ret = 0;
+ int ret = EXIT_SUCCESS;
// TODO remainder of auth to dhcp6 code copy. We need to enable this in
// dhcp6 eventually
@@ -99,13 +110,13 @@ main(int argc, char* argv[]) {
cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
- Dhcpv6Srv* srv = new Dhcpv6Srv();
+ Dhcpv6Srv* srv = new Dhcpv6Srv(port_number);
srv->run();
} catch (const std::exception& ex) {
cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
- ret = 1;
+ ret = EXIT_FAILURE;
}
return (ret);
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 6a0844f..a1a11a0 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -1,11 +1,10 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-#PYTESTS = args_test.py bind10_test.py
-# NOTE: this has a generated test found in the builddir
PYTESTS = dhcp6_test.py
EXTRA_DIST = $(PYTESTS)
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
+# Explicitly specify paths to dynamic libraries required by loadable python
+# modules. That is required on Mac OS systems. Otherwise we will get exception
+# about python not being able to load liblog library.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
@@ -15,9 +14,8 @@ endif
check-local:
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
- BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
@@ -26,8 +24,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
AM_CPPFLAGS += -I$(top_srcdir)/src/bin
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt
@@ -50,7 +46,6 @@ dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp6_unittests_LDADD = $(GTEST_LDADD)
-dhcp6_unittests_LDADD += $(SQLITE_LIBS)
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index f4cae66..5e98e18 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -35,7 +35,6 @@ using namespace isc::util;
// namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv6Srv: public Dhcpv6Srv {
// "naked" Interface Manager, exposes internal fields
@@ -54,18 +53,10 @@ public:
class Dhcpv6SrvTest : public ::testing::Test {
public:
+ // these are empty for now, but let's keep them around
Dhcpv6SrvTest() {
- unlink(INTERFACE_FILE);
- fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
- if (if_nametoindex("lo") > 0) {
- fakeifaces << "lo ::1";
- } else if (if_nametoindex("lo0") > 0) {
- fakeifaces << "lo0 ::1";
- }
- fakeifaces.close();
}
~Dhcpv6SrvTest() {
- unlink(INTERFACE_FILE);
};
};
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index e080c77..cf04f60 100644
--- a/src/bin/dhcp6/tests/dhcp6_test.py
+++ b/src/bin/dhcp6/tests/dhcp6_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Internet Systems Consortium.
+# Copyright (C) 2011,2012 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -23,52 +23,150 @@ import socket
from isc.net.addr import IPAddr
import time
import isc
+import fcntl
class TestDhcpv6Daemon(unittest.TestCase):
def setUp(self):
+ # don't redirect stdout/stderr here as we want to print out things
+ # during the test
+ pass
+
+ def tearDown(self):
+ pass
+
+ def runCommand(self, params, wait=1):
+ """
+ This method runs a command and returns a touple: (returncode, stdout, stderr)
+ """
+ ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
+
+ print("Running command: %s" % (" ".join(params)))
+
# redirect stdout to a pipe so we can check that our
# process spawning is doing the right thing with stdout
- self.old_stdout = os.dup(sys.stdout.fileno())
- self.pipes = os.pipe()
- os.dup2(self.pipes[1], sys.stdout.fileno())
- os.close(self.pipes[1])
+ self.stdout_old = os.dup(sys.stdout.fileno())
+ self.stdout_pipes = os.pipe()
+ os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
+ os.close(self.stdout_pipes[1])
+
+ # do the same trick for stderr:
+ self.stderr_old = os.dup(sys.stderr.fileno())
+ self.stderr_pipes = os.pipe()
+ os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
+ os.close(self.stderr_pipes[1])
+
# note that we use dup2() to restore the original stdout
# to the main program ASAP in each test... this prevents
# hangs reading from the child process (as the pipe is only
# open in the child), and also insures nice pretty output
- print("Please ignore any socket errors. Purpose of this test is to")
- print("verify that DHCPv6 process could be started, not that socket")
- print("could be bound. Binding fails when run as non-root user.")
- def tearDown(self):
- # clean up our stdout munging
- os.dup2(self.old_stdout, sys.stdout.fileno())
- os.close(self.pipes[0])
+ pi = ProcessInfo('Test Process', params)
+ pi.spawn()
+ time.sleep(wait)
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+ # Set non-blocking read on pipes. Process may not print anything
+ # on specific output and the we would hang without this.
+ fd = self.stdout_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ fd = self.stderr_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ # There's potential problem if b10-dhcp4 prints out more
+ # than 4k of text
+ try:
+ output = os.read(self.stdout_pipes[0], 4096)
+ except OSError:
+ print("No data available from stdout")
+ output = ""
+
+ # read can return None. Make sure we have a string
+ if (output is None):
+ output = ""
+
+ try:
+ error = os.read(self.stderr_pipes[0], 4096)
+ except OSError:
+ print("No data available on stderr")
+ error = ""
+
+ # read can return None. Make sure we have a string
+ if (error is None):
+ error = ""
+
+ try:
+ if (not pi.process.poll()):
+ # let's be nice at first...
+ pi.process.terminate()
+ except OSError:
+ print("Ignoring failed kill attempt. Process is dead already.")
+
+ # call this to get returncode, process should be dead by now
+ rc = pi.process.wait()
+
+ # Clean up our stdout/stderr munging.
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.close(self.stdout_pipes[0])
+
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ os.close(self.stderr_pipes[0])
+
+ print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
+ % (rc, len(output), len(error)) )
+
+ return (rc, output, error)
def test_alive(self):
"""
Simple test. Checks that b10-dhcp6 can be started and prints out info
about starting DHCPv6 operation.
"""
- pi = ProcessInfo('Test Process', [ '../b10-dhcp6' , '-v' ])
- pi.spawn()
- time.sleep(1)
- os.dup2(self.old_stdout, sys.stdout.fileno())
- self.assertNotEqual(pi.process, None)
- self.assertTrue(type(pi.pid) is int)
- output = os.read(self.pipes[0], 4096)
+ print("Note: Purpose of some of the tests is to check if DHCPv6 server can be started,")
+ print(" not that is can bind sockets correctly. Please ignore binding errors.")
+ (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
+
self.assertEqual( str(output).count("[b10-dhcp6] Initiating DHCPv6 operation."), 1)
- # kill this process
- # XXX: b10-dhcp6 is too dumb to understand 'shutdown' command for now,
- # so let's just kill the bastard
+ def test_portnumber_0(self):
+ print("Check that specifying port number 0 is not allowed.")
- # TODO: Ignore errors for now. This test will be more thorough once ticket #1503
- # (passing port number to b10-dhcp6 daemon) is implemented.
- try:
- os.kill(pi.pid, signal.SIGTERM)
- except OSError:
- print("Ignoring failed kill attempt. Process is dead already.")
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '0'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+ def test_portnumber_missing(self):
+ print("Check that -p option requires a parameter.")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("option requires an argument"), 1)
+
+ def test_portnumber_nonroot(self):
+ print("Check that specifying unprivileged port number will work.")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '10057'])
+
+ # When invalid port number is specified, return code must not be success
+ # TODO: Temporarily commented out as socket binding on systems that do not have
+ # interface detection implemented currently fails.
+ # self.assertTrue(returncode == 0)
+
+ # Check that there is a message on stdout about opening proper port
+ self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/resolver/b10-resolver.8 b/src/bin/resolver/b10-resolver.8
deleted file mode 100644
index eed69b8..0000000
--- a/src/bin/resolver/b10-resolver.8
+++ /dev/null
@@ -1,140 +0,0 @@
-'\" t
-.\" Title: b10-resolver
-.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\" Date: February 28, 2012
-.\" Manual: BIND10
-.\" Source: BIND10
-.\" Language: English
-.\"
-.TH "B10\-RESOLVER" "8" "February 28, 2012" "BIND10" "BIND10"
-.\" -----------------------------------------------------------------
-.\" * set default formatting
-.\" -----------------------------------------------------------------
-.\" disable hyphenation
-.nh
-.\" disable justification (adjust text to left margin only)
-.ad l
-.\" -----------------------------------------------------------------
-.\" * MAIN CONTENT STARTS HERE *
-.\" -----------------------------------------------------------------
-.SH "NAME"
-b10-resolver \- Recursive DNS server
-.SH "SYNOPSIS"
-.HP \w'\fBb10\-resolver\fR\ 'u
-\fBb10\-resolver\fR [\fB\-v\fR]
-.SH "DESCRIPTION"
-.PP
-The
-\fBb10\-resolver\fR
-daemon provides the BIND 10 recursive DNS server\&. Normally it is started by the
-\fBbind10\fR(8)
-boss process\&.
-.PP
-This daemon communicates with other BIND 10 components over a
-\fBb10-msgq\fR(8)
-C\-Channel connection\&. If this connection is not established,
-\fBb10\-resolver\fR
-will exit\&.
-.PP
-It also receives its configurations from
-\fBb10-cfgmgr\fR(8)\&.
-.SH "OPTIONS"
-.PP
-The arguments are as follows:
-.PP
-\fB\-v\fR
-.RS 4
-Enable verbose mode\&. This sets logging to the maximum debugging level\&.
-.RE
-.SH "CONFIGURATION AND COMMANDS"
-.PP
-The configurable settings are:
-.PP
-
-\fIforward_addresses\fR
-defines the list of addresses and ports that
-\fBb10\-resolver\fR
-should forward queries to\&. Defining this enables forwarding\&.
-.PP
-
-\fIlisten_on\fR
-is a list of addresses and ports for
-\fBb10\-resolver\fR
-to listen on\&. The list items are the
-\fIaddress\fR
-string and
-\fIport\fR
-number\&. The defaults are address ::1 port 53 and address 127\&.0\&.0\&.1 port 53\&.
-.PP
-
-
-
-
-
-
-\fIquery_acl\fR
-is a list of query access control rules\&. The list items are the
-\fIaction\fR
-string and the
-\fIfrom\fR
-or
-\fIkey\fR
-strings\&. The possible actions are ACCEPT, REJECT and DROP\&. The
-\fIfrom\fR
-is a remote (source) IPv4 or IPv6 address or special keyword\&. The
-\fIkey\fR
-is a TSIG key name\&. The default configuration accepts queries from 127\&.0\&.0\&.1 and ::1\&.
-.PP
-
-\fIretries\fR
-is the number of times to retry (resend query) after a query timeout (\fItimeout_query\fR)\&. The default is 3\&.
-.PP
-
-\fIroot_addresses\fR
-is a list of addresses and ports for
-\fBb10\-resolver\fR
-to use directly as root servers to start resolving\&. The list items are the
-\fIaddress\fR
-string and
-\fIport\fR
-number\&. By default, a hardcoded address for l\&.root\-servers\&.net (199\&.7\&.83\&.42 or 2001:500:3::42) is used\&.
-.PP
-
-\fItimeout_client\fR
-is the number of milliseconds to wait before timing out the incoming client query\&. If set to \-1, this timeout is disabled\&. The default is 4000\&. After this timeout, a SERVFAIL is sent back to the client asking the question\&. (The lookup may continue after the timeout, but a later answer is not returned for the now\-past query\&.)
-.PP
-
-\fItimeout_lookup\fR
-is the number of milliseconds before it stops trying the query\&. If set to \-1, this timeout is disabled\&. The default is 30000\&.
-.PP
-
-
-\fItimeout_query\fR
-is the number of milliseconds to wait before it retries a query\&. If set to \-1, this timeout is disabled\&. The default is 2000\&.
-.PP
-The configuration command is:
-.PP
-
-\fBshutdown\fR
-exits
-\fBb10\-resolver\fR\&. This has an optional
-\fIpid\fR
-argument to select the process ID to stop\&. (Note that the BIND 10 boss process may restart this service if configured\&.)
-.SH "SEE ALSO"
-.PP
-
-\fBb10-cfgmgr\fR(8),
-\fBb10-cmdctl\fR(8),
-\fBb10-msgq\fR(8),
-\fBbind10\fR(8),
-BIND 10 Guide\&.
-.SH "HISTORY"
-.PP
-The
-\fBb10\-resolver\fR
-daemon was first coded in September 2010\&. The initial implementation only provided forwarding\&. Iteration was introduced in January 2011\&. Caching was implemented in February 2011\&. Access control was introduced in June 2011\&.
-.SH "COPYRIGHT"
-.br
-Copyright \(co 2010-2012 Internet Systems Consortium, Inc. ("ISC")
-.br
diff --git a/src/bin/sockcreator/Makefile.am b/src/bin/sockcreator/Makefile.am
index e954c02..8494114 100644
--- a/src/bin/sockcreator/Makefile.am
+++ b/src/bin/sockcreator/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = tests
+SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
index 63830a5..2354521 100644
--- a/src/lib/asiolink/io_endpoint.cc
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -14,10 +14,6 @@
#include <config.h>
-#include <unistd.h> // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
#include <asio.hpp>
#include <asiolink/io_address.h>
@@ -26,6 +22,13 @@
#include <asiolink/tcp_endpoint.h>
#include <asiolink/udp_endpoint.h>
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
using namespace std;
namespace isc {
@@ -58,5 +61,18 @@ IOEndpoint::operator!=(const IOEndpoint& other) const {
return (!operator==(other));
}
+ostream&
+operator<<(ostream& os, const IOEndpoint& endpoint) {
+ if (endpoint.getFamily() == AF_INET6) {
+ os << "[" << endpoint.getAddress().toText() << "]";
+ } 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 << ":" << boost::lexical_cast<string>(endpoint.getPort());
+ return (os);
+}
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
index 11ea97b..dd74036 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/src/lib/asiolink/io_endpoint.h
@@ -18,9 +18,6 @@
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
// See the description of the namespace below.
-#include <unistd.h> // for some network system calls
-
-#include <sys/socket.h> // for sockaddr
#include <functional>
#include <string>
@@ -28,6 +25,12 @@
#include <exceptions/exceptions.h>
#include <asiolink/io_address.h>
+# include <ostream>
+
+#include <unistd.h> // for some network system calls
+
+#include <sys/socket.h> // for sockaddr
+
namespace isc {
namespace asiolink {
@@ -158,6 +161,27 @@ public:
const unsigned short port);
};
+/// \brief Insert the \c IOEndpoint as a string into stream.
+///
+/// This method converts \c endpoint into a string and inserts it into the
+/// output stream \c os.
+///
+/// This method converts the address and port of the endpoint in the textual
+/// format that other BIND 10 modules would use in logging, i.e.,
+/// - For IPv6 address: [<address>]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: <address>:port (e.g., 192.0.2.53:5300)
+///
+/// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
+/// same format as that for IPv4, although in practice such a case is not
+/// really expected.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param endpoint A reference to an \c IOEndpoint 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 IOEndpoint& endpoint);
} // namespace asiolink
} // namespace isc
#endif // __IO_ENDPOINT_H
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 984cf07..eaa2173 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -33,11 +33,11 @@ run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index 948e708..462a2fb 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -13,18 +13,22 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
#include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+
+#include <sstream>
+#include <string>
+
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
-#include <boost/shared_ptr.hpp>
-
-#include <asiolink/io_endpoint.h>
-#include <asiolink/io_error.h>
-
using namespace isc::asiolink;
namespace {
@@ -240,4 +244,51 @@ TEST(IOEndpointTest, getSockAddr) {
sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
}
+// A faked IOEndpoint for an uncommon address family. It wouldn't be possible
+// to create via the normal factory, so we define a special derived class
+// for it.
+class TestIOEndpoint : public IOEndpoint {
+ virtual IOAddress getAddress() const {
+ return IOAddress("2001:db8::bad:add");
+ }
+ virtual uint16_t getPort() const { return (42); }
+ virtual short getProtocol() const { return (IPPROTO_UDP); }
+ virtual short getFamily() const { return (AF_UNSPEC); }
+ virtual const struct sockaddr& getSockAddr() const {
+ static struct sockaddr sa_placeholder;
+ return (sa_placeholder);
+ }
+};
+
+void
+checkEndpointText(const std::string& expected, const IOEndpoint& ep) {
+ std::ostringstream oss;
+ oss << ep;
+ EXPECT_EQ(expected, oss.str());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(IOEndpointTest, LeftShiftOperator) {
+ // UDP/IPv4
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+
+ // UDP/IPv6
+ ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Same for TCP: shouldn't be different
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Uncommon address family. The actual behavior doesn't matter much
+ // in practice, but we check such input doesn't make it crash.
+ // We explicitly instantiate the test EP because otherwise some compilers
+ // would be confused and complain.
+ TestIOEndpoint test_ep;
+ checkEndpointText("2001:db8::bad:add:42", test_ep);
+}
}
diff --git a/src/lib/cache/tests/message_entry_unittest.cc b/src/lib/cache/tests/message_entry_unittest.cc
index d9709ed..86cc89f 100644
--- a/src/lib/cache/tests/message_entry_unittest.cc
+++ b/src/lib/cache/tests/message_entry_unittest.cc
@@ -1,3 +1,5 @@
+// Copyright (C) 2010 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.
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
index 08b7f33..e49b599 100644
--- a/src/lib/cc/tests/Makefile.am
+++ b/src/lib/cc/tests/Makefile.am
@@ -26,7 +26,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
# We need to put our libs first, in case gtest (or any dependency, really)
# is installed in the same location as a different version of bind10
-# Otherwise the linker may not use the source tree libs
+# Otherwise the linker may not use the source tree libs
run_unittests_LDADD = $(top_builddir)/src/lib/cc/libcc.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index 500ff12..518d497 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -17,6 +17,11 @@ libcfgclient_la_SOURCES += module_spec.h module_spec.cc
libcfgclient_la_SOURCES += ccsession.cc ccsession.h
libcfgclient_la_SOURCES += config_log.h config_log.cc
+libcfgclient_la_LIBADD = $(top_builddir)/src/lib/cc/libcc.la
+libcfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+libcfgclient_la_LDFLAGS = -no-undefined -version-info 1:0:1
+
nodist_libcfgclient_la_SOURCES = config_messages.h config_messages.cc
# The message file should be in the distribution.
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 63fa4cd..d4c6653 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -601,6 +601,11 @@ ModuleCCSession::checkCommand() {
ConstElementPtr cmd, routing, data;
if (session_.group_recvmsg(routing, data, true)) {
+ // In case the message is wanted asynchronously, it gets used.
+ if (checkAsyncRecv(routing, data)) {
+ return (0);
+ }
+
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
if (data->getType() != Element::map || data->contains("result")) {
@@ -764,5 +769,95 @@ ModuleCCSession::sendStopping() {
session_.group_sendmsg(cmd, "ConfigManager");
}
+class ModuleCCSession::AsyncRecvRequest {
+public: // Everything is public here, as the definition is hidden anyway
+ AsyncRecvRequest(const AsyncRecvCallback& cb, const string& rcp, int sq,
+ bool reply) :
+ callback(cb),
+ recipient(rcp),
+ seq(sq),
+ is_reply(reply)
+ {}
+ const AsyncRecvCallback callback;
+ const string recipient;
+ const int seq;
+ const bool is_reply;
+};
+
+ModuleCCSession::AsyncRecvRequestID
+ModuleCCSession::groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq,
+ const string& recipient) {
+ // This just stores the request, the handling is done in checkCommand()
+
+ // push_back would be simpler, but it does not return the iterator we need
+ return (async_recv_requests_.insert(async_recv_requests_.end(),
+ AsyncRecvRequest(callback, recipient,
+ seq, is_reply)));
+}
+
+bool
+ModuleCCSession::checkAsyncRecv(const ConstElementPtr& envelope,
+ const ConstElementPtr& msg)
+{
+ for (AsyncRecvRequestID request(async_recv_requests_.begin());
+ request != async_recv_requests_.end(); ++request) {
+ // Just go through all the requests and look for a matching one
+ if (requestMatch(*request, envelope)) {
+ // We want the request to be still alive at the time we
+ // call the callback. But we need to remove it on an exception
+ // too, so we use the class. If just C++ had the finally keyword.
+ class RequestDeleter {
+ public:
+ RequestDeleter(AsyncRecvRequests& requests,
+ AsyncRecvRequestID& request) :
+ requests_(requests),
+ request_(request)
+ { }
+ ~RequestDeleter() {
+ requests_.erase(request_);
+ }
+ private:
+ AsyncRecvRequests& requests_;
+ AsyncRecvRequestID& request_;
+ };
+ RequestDeleter deleter(async_recv_requests_, request);
+ // Call the callback
+ request->callback(envelope, msg, request);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+ModuleCCSession::requestMatch(const AsyncRecvRequest& request,
+ const ConstElementPtr& envelope) const
+{
+ if (request.is_reply != envelope->contains("reply")) {
+ // Wrong type of message
+ return (false);
+ }
+ if (request.is_reply &&
+ (request.seq == -1 ||
+ request.seq == envelope->get("reply")->intValue())) {
+ // This is the correct reply
+ return (true);
+ }
+ if (!request.is_reply &&
+ (request.recipient.empty() ||
+ request.recipient == envelope->get("group")->stringValue())) {
+ // This is the correct command
+ return (true);
+ }
+ // If nothing from the above, we don't want it
+ return (false);
+}
+
+void
+ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
+ async_recv_requests_.erase(id);
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 059968c..e96a33d 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -15,13 +15,16 @@
#ifndef __CCSESSION_H
#define __CCSESSION_H 1
-#include <string>
-
#include <config/config_data.h>
#include <config/module_spec.h>
+
#include <cc/session.h>
#include <cc/data.h>
+#include <string>
+#include <list>
+#include <boost/function.hpp>
+
namespace isc {
namespace config {
@@ -358,15 +361,140 @@ public:
return (session_.group_recvmsg(envelope, msg, nonblock, seq));
};
+ /// \brief Forward declaration of internal data structure.
+ ///
+ /// This holds information about one asynchronous request to receive
+ /// a message. It is declared as public to allow declaring other derived
+ /// types, but without showing the internal representation.
+ class AsyncRecvRequest;
+
+ /// \brief List of all requests for asynchronous reads.
+ typedef std::list<AsyncRecvRequest> AsyncRecvRequests;
+
+ /// \brief Identifier of single request for asynchronous read.
+ typedef AsyncRecvRequests::iterator AsyncRecvRequestID;
+
+ /// \brief Callback which is called when an asynchronous receive finishes.
+ ///
+ /// This is the callback used by groupRecvMsgAsync() function. It is called
+ /// when a matching message arrives. It receives following parameters when
+ /// called:
+ /// - The envelope of the message
+ /// - The message itself
+ /// - The ID of the request, as returned by corresponding groupRecvMsgAsync
+ /// call.
+ ///
+ /// It is possible to throw exceptions from the callback, but they will not
+ /// be caught and they will get propagated out through the checkCommand()
+ /// call. This, if not handled on higher level, will likely terminate the
+ /// application. However, the ModuleCCSession internals will be in
+ /// well-defined state after the call (both the callback and the message
+ /// will be removed from the queues as already called).
+ typedef boost::function3<void, const isc::data::ConstElementPtr&,
+ const isc::data::ConstElementPtr&,
+ const AsyncRecvRequestID&>
+ AsyncRecvCallback;
+
+ /// \brief Receive a message from the CC session asynchronously.
+ ///
+ /// This registers a callback which is called when a matching message
+ /// is received. This message returns immediately.
+ ///
+ /// Once a matching message arrives, the callback is called with the
+ /// envelope of the message, the message itself and the result of this
+ /// function call (which might be useful for identifying which of many
+ /// events the recipient is waiting for this is). This makes the callback
+ /// used and is not called again even if a message that would match
+ /// arrives later (this is a single-shot callback).
+ ///
+ /// The callback is never called from within this function. Even if there
+ /// are queued messages, the callback would be called once checkCommand()
+ /// is invoked (possibly from start() or the constructor).
+ ///
+ /// The matching is as follows. If is_reply is true, only replies are
+ /// considered. In that case, if seq is -1, any reply is accepted. If
+ /// it is something else than -1, only the reply with matching seq is
+ /// taken. This may be used to receive replies to commands
+ /// asynchronously.
+ ///
+ /// In case the is_reply is false, the function looks for command messages.
+ /// The seq parameter is ignored, but the recipient one is considered. If
+ /// it is an empty string, any command is taken. If it is non-empty, only
+ /// commands addressed to the recipient channel (eg. group - instance is
+ /// ignored for now) are taken. This can be used to receive foreign commands
+ /// or notifications. In such case, it might be desirable to call the
+ /// groupRecvMsgAsync again from within the callback, to receive any future
+ /// commands or events of the same type.
+ ///
+ /// The interaction with other receiving functions is slightly complicated.
+ /// The groupRecvMsg call takes precedence. If the message matches its
+ /// parameters, it steals the message and no callback matching it as well
+ /// is called. Then, all the queued asynchronous receives are considered,
+ /// with the oldest active ones taking precedence (they work as FIFO).
+ /// If none of them matches, generic command and config handling takes
+ /// place. If it is not handled by that, the message is dropped. However,
+ /// it is better if there's just one place that wants to receive each given
+ /// message.
+ ///
+ /// \exception std::bad_alloc if there isn't enough memory to store the
+ /// callback.
+ /// \param callback is the function to be called when a matching message
+ /// arrives.
+ /// \param is_reply specifies if the desired message should be a reply or
+ /// a command.
+ /// \param seq specifies the reply sequence number in case a reply is
+ /// desired. The default -1 means any reply is OK.
+ /// \param recipient is the CC channel to which the command should be
+ /// addressed to match (in case is_reply is false). Empty means any
+ /// command is good one.
+ /// \return An identifier of the request. This will be passed to the
+ /// callback or can be used to cancel the request by cancelAsyncRecv.
+ /// \todo Decide what to do with instance and what was it meant for anyway.
+ AsyncRecvRequestID groupRecvMsgAsync(const AsyncRecvCallback& callback,
+ bool is_reply, int seq = -1,
+ const std::string& recipient =
+ std::string());
+
+ /// \brief Removes yet unused request for asynchronous receive.
+ ///
+ /// This function cancels a request previously queued by
+ /// groupRecvMsgAsync(). You may use it only before the callback was
+ /// already triggered. If you call it with an ID of callback that
+ /// already happened or was already canceled, the behaviour is undefined
+ /// (but something like a crash is very likely, as the function removes
+ /// an item from a list and this would be removing it from a list that
+ /// does not contain the item).
+ ///
+ /// It is important to cancel requests that are no longer going to happen
+ /// for some reason, as the request would occupy memory forever.
+ ///
+ /// \param id The id of request as returned by groupRecvMsgAsync.
+ void cancelAsyncRecv(const AsyncRecvRequestID& id);
+
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
void sendStopping();
+ /// \brief Check if the message is wanted by asynchronous read
+ ///
+ /// It checks if any of the previously queued requests match
+ /// the message. If so, the callback is dispatched and removed.
+ ///
+ /// \param envelope The envelope of the message.
+ /// \param msg The actual message data.
+ /// \return True if the message was used for a callback, false
+ /// otherwise.
+ bool checkAsyncRecv(const data::ConstElementPtr& envelope,
+ const data::ConstElementPtr& msg);
+ /// \brief Checks if a message with this envelope matches the request
+ bool requestMatch(const AsyncRecvRequest& request,
+ const data::ConstElementPtr& envelope) const;
bool started_;
std::string module_name_;
isc::cc::AbstractSession& session_;
ModuleSpec module_specification_;
+ AsyncRecvRequests async_recv_requests_;
isc::data::ConstElementPtr handleConfigUpdate(
isc::data::ConstElementPtr new_config);
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index a931070..98a991d 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -136,7 +136,7 @@ check_statistics_item_list(ConstElementPtr spec) {
&& item->contains("item_default")) {
if(!check_format(item->get("item_default"),
item->get("item_format"))) {
- isc_throw(ModuleSpecError,
+ isc_throw(ModuleSpecError,
"item_default not valid type of item_format");
}
}
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index abaff8e..3fca741 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -27,11 +27,13 @@
#include <log/logger_name.h>
#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
using namespace isc::data;
using namespace isc::config;
using namespace isc::cc;
using namespace std;
+using namespace boost;
namespace {
std::string
@@ -497,10 +499,10 @@ TEST_F(CCSessionTest, remoteConfig) {
const size_t qsize(session.getMsgQueue()->size());
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": ["
- "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] } ]")));
+ "\"get_module_spec\", { \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_TRUE(session.getMsgQueue()->get(qsize - 1)->equals(*el(
"[ \"ConfigManager\", \"*\", { \"command\": [ \"get_config\","
- "{ \"module_name\": \"Spec2\" } ] } ]")));
+ "{ \"module_name\": \"Spec2\" } ] }, -1 ]")));
EXPECT_EQ("Spec2", module_name);
// Since we returned an empty local config above, the default value
// for "item1", which is 1, should be used.
@@ -709,13 +711,286 @@ TEST_F(CCSessionTest, doubleStartWithAddRemoteConfig) {
FakeSession::DoubleRead);
}
-namespace {
+/// \brief Test fixture for asynchronous receiving of messages.
+///
+/// This is an extension to the CCSessionTest. It would be possible to add
+/// the functionality to the CCSessionTest, but it is going to be used
+/// only by few tests and is non-trivial, so it is placed to a separate
+/// sub-class.
+class AsyncReceiveCCSessionTest : public CCSessionTest {
+protected:
+ AsyncReceiveCCSessionTest() :
+ mccs_(ccspecfile("spec29.spec"), session, NULL, NULL, false, false),
+ msg_(el("{\"result\": [0]}")),
+ next_flag_(0)
+ {
+ // This is just to make sure the messages get through the fake
+ // session.
+ session.subscribe("test group");
+ session.subscribe("other group");
+ session.subscribe("<ignored>");
+ // Get rid of all unrelated stray messages
+ while (session.getMsgQueue()->size() > 0) {
+ session.getMsgQueue()->remove(0);
+ }
+ }
+ /// \brief Convenience function to queue a request to get a command
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerCommand(const string& recipient)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), false, -1, recipient));
+ }
+ /// \brief Convenience function to queue a request to get a reply
+ /// message.
+ ModuleCCSession::AsyncRecvRequestID
+ registerReply(int seq)
+ {
+ return (mccs_.groupRecvMsgAsync(
+ bind(&AsyncReceiveCCSessionTest::callback, this, next_flag_ ++, _1,
+ _2, _3), true, seq));
+ }
+ /// \brief Check the next called callback was with this flag
+ void called(int flag) {
+ ASSERT_FALSE(called_.empty());
+ EXPECT_EQ(flag, *called_.begin());
+ called_.pop_front();
+ }
+ /// \brief Checks that no more callbacks were called.
+ void nothingCalled() {
+ EXPECT_TRUE(called_.empty());
+ }
+ /// \brief The tested session.
+ ModuleCCSession mccs_;
+ /// \brief The value of message on the last called callback.
+ ConstElementPtr last_msg_;
+ /// \brief A message that can be used
+ ConstElementPtr msg_;
+ // Shared part of the simpleCommand and similar tests.
+ void commandTest(const string& group) {
+ // Push the message inside
+ session.addMessage(msg_, "test group", "<unused>");
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerCommand(group);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "<unused>");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the simpleResponse and wildcardResponse tests.
+ void responseTest(int seq) {
+ // Push the message inside
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ registerReply(seq);
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // But when we call the checkCommand(), it should be called.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+ // And the message should be eaten
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ // The callback should have been eaten as well, inserting another
+ // message will not invoke it again
+ session.addMessage(msg_, "test group", "<unused>");
+ mccs_.checkCommand();
+ nothingCalled();
+ }
+ /// \brief Shared part of the noMatch* tests
+ void noMatchTest(int seq, int wanted_seq, bool is_reply) {
+ // Push the message inside
+ session.addMessage(msg_, "other group", "<unused>", seq);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // Register the callback
+ if (is_reply) {
+ registerReply(wanted_seq);
+ } else {
+ registerCommand("test group");
+ }
+ // But the callback should not be called yet
+ // (even if the message is there).
+ nothingCalled();
+ // And even not now, because it does not match.
+ mccs_.checkCommand();
+ nothingCalled();
+ // And the message should be eaten by the checkCommand
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ }
+private:
+ /// \brief The next flag to be handed out
+ int next_flag_;
+ /// \brief Flags of callbacks already called (as FIFO)
+ list<int> called_;
+ /// \brief This is the callback registered to the tested groupRecvMsgAsync
+ /// function.
+ void callback(int store_flag, const ConstElementPtr&,
+ const ConstElementPtr& msg,
+ const ModuleCCSession::AsyncRecvRequestID&)
+ {
+ called_.push_back(store_flag);
+ last_msg_ = msg;
+ }
+};
+
+// Test we can receive a command, without anything fancy yet
+TEST_F(AsyncReceiveCCSessionTest, simpleCommand) {
+ commandTest("test group");
+}
+
+// Test we can receive a "wildcard" command - without specifying the
+// group to subscribe to. Very similar to simpleCommand test.
+TEST_F(AsyncReceiveCCSessionTest, wildcardCommand) {
+ commandTest("");
+}
+
+// Very similar to simpleCommand, but with a response message
+TEST_F(AsyncReceiveCCSessionTest, simpleResponse) {
+ responseTest(1);
+}
+
+// Matching a response message with wildcard
+TEST_F(AsyncReceiveCCSessionTest, wildcardResponse) {
+ responseTest(-1);
+}
+
+// Check that a wrong command message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommand) {
+ noMatchTest(-1, -1, false);
+}
+
+// Check that a wrong response message is not matched
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponse) {
+ noMatchTest(2, 3, true);
+}
+
+// Check that a command will not match on a reply check and vice versa
+TEST_F(AsyncReceiveCCSessionTest, noMatchResponseAgainstCommand) {
+ // Send a command and check it is not matched as a response
+ noMatchTest(-1, -1, true);
+}
+
+TEST_F(AsyncReceiveCCSessionTest, noMatchCommandAgainstResponse) {
+ noMatchTest(2, -1, false);
+}
+
+// We check for command several times before the message actually arrives.
+TEST_F(AsyncReceiveCCSessionTest, delayedCallback) {
+ // First, register the callback
+ registerReply(1);
+ // And see it is not called, because the message is not there yet
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ for (size_t i(0); i < 100; ++ i) {
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+ }
+ // Now the message finally arrives
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, the callback is happily triggered.
+ mccs_.checkCommand();
+ called(0);
+ EXPECT_EQ(msg_, last_msg_);
+ // But only once
+ nothingCalled();
+}
+
+// See that if we put multiple messages inside, and request some callbacks,
+// the callbacks are called in the order of messages, not in the order they
+// were registered.
+TEST_F(AsyncReceiveCCSessionTest, outOfOrder) {
+ // First, put some messages there
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ session.addMessage(msg_, "test group", "<unused>");
+ session.addMessage(msg_, "other group", "<unused>");
+ session.addMessage(msg_, "<ignored>", "<unused>", 2);
+ session.addMessage(msg_, "<ignored>", "<unused>", 3);
+ session.addMessage(msg_, "<ignored>", "<unused>", 4);
+ // Now register some callbacks
+ registerReply(13); // Will not be called
+ registerCommand("other group"); // Matches 3rd message
+ registerReply(2); // Matches 4th message
+ registerCommand(""); // Matches the 2nd message
+ registerCommand("test group"); // Will not be called
+ registerReply(-1); // Matches the 1st message
+ registerReply(-1); // Matches the 5th message
+ // Process all messages there
+ while (mccs_.hasQueuedMsgs()) {
+ mccs_.checkCommand();
+ }
+ // These are the numbers of callbacks in the order of messages
+ called(5);
+ called(3);
+ called(1);
+ called(2);
+ called(6);
+ // The last message doesn't trigger anything, so nothing more is called
+ nothingCalled();
+}
+
+// We first add, then remove the callback again and check that nothing is
+// matched.
+TEST_F(AsyncReceiveCCSessionTest, cancel) {
+ // Add the callback
+ ModuleCCSession::AsyncRecvRequestID request(registerReply(1));
+ // Add corresponding message
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ EXPECT_TRUE(mccs_.hasQueuedMsgs());
+ // And now, remove the callback again
+ mccs_.cancelAsyncRecv(request);
+ // And see that Nothing Happens(TM)
+ mccs_.checkCommand();
+ EXPECT_FALSE(mccs_.hasQueuedMsgs());
+ nothingCalled();
+}
+
+// We add multiple requests and cancel only one of them to see the rest
+// is unaffected.
+TEST_F(AsyncReceiveCCSessionTest, cancelSome) {
+ // Register few callbacks
+ registerReply(1);
+ ModuleCCSession::AsyncRecvRequestID request(registerCommand(""));
+ registerCommand("test group");
+ // Put some messages there
+ session.addMessage(msg_, "test group", "<unused>");
+ session.addMessage(msg_, "<ignored>", "<unused>", 1);
+ // Cancel the second callback. Therefore the first message will be matched
+ // by the third callback, not by the second.
+ mccs_.cancelAsyncRecv(request);
+ // Now, process the messages
+ mccs_.checkCommand();
+ mccs_.checkCommand();
+ // And see how they matched
+ called(2);
+ called(0);
+ nothingCalled();
+}
+
void doRelatedLoggersTest(const char* input, const char* expected) {
ConstElementPtr all_conf = isc::data::Element::fromJSON(input);
ConstElementPtr expected_conf = isc::data::Element::fromJSON(expected);
EXPECT_EQ(*expected_conf, *isc::config::getRelatedLoggers(all_conf));
}
-} // end anonymous namespace
TEST(LogConfigTest, relatedLoggersTest) {
// make sure logger configs for 'other' programs are ignored,
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 177e629..157d4d6 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -139,6 +139,9 @@ FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg, bool nonblock,
ElementPtr new_env = Element::createMap();
new_env->set("group", c_m->get(0));
new_env->set("to", c_m->get(1));
+ if (c_m->get(3)->intValue() != -1) {
+ new_env->set("reply", c_m->get(3));
+ }
env = new_env;
msg = c_m->get(2);
to_remove = c_m;
@@ -207,7 +210,7 @@ FakeSession::reply(ConstElementPtr envelope, ConstElementPtr newmsg) {
bool
FakeSession::hasQueuedMsgs() const {
- return (false);
+ return (msg_queue_ && msg_queue_->size() > 0);
}
ConstElementPtr
@@ -228,12 +231,13 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
void
FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
- const std::string& to)
+ const std::string& to, int seq)
{
ElementPtr m_el = Element::createList();
m_el->add(Element::create(group));
m_el->add(Element::create(to));
m_el->add(msg);
+ m_el->add(Element::create(seq));
if (!msg_queue_) {
msg_queue_ = Element::createList();
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 79ff174..c91b519 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -74,7 +74,7 @@ public:
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
void addMessage(isc::data::ConstElementPtr, const std::string& group,
- const std::string& to);
+ const std::string& to, int seq = -1);
bool haveSubscription(const std::string& group,
const std::string& instance);
bool haveSubscription(const isc::data::ConstElementPtr group,
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 7b271f1..62fa61e 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -839,8 +839,6 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
flags));
} else if (wti != found.second.end()) {
bool any(type == RRType::ANY());
- isc::log::MessageID lid(wild ? DATASRC_DATABASE_WILDCARD_MATCH :
- DATASRC_DATABASE_FOUND_RRSET);
if (any) {
// An ANY query, copy everything to the target instead of returning
// directly.
@@ -851,15 +849,32 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
target->push_back(it->second);
}
}
- lid = wild ? DATASRC_DATABASE_WILDCARD_ANY :
- DATASRC_DATABASE_FOUND_ANY;
+ if (wild) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_ANY).
+ arg(accessor_->getDBName()).arg(name);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_ANY).
+ arg(accessor_->getDBName()).arg(name);
+ }
+ } else {
+ if (wild) {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_WILDCARD_MATCH).
+ arg(accessor_->getDBName()).arg(*wildname).
+ arg(wti->second);
+ } else {
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED,
+ DATASRC_DATABASE_FOUND_RRSET).
+ arg(accessor_->getDBName()).arg(wti->second);
+ }
}
// Found an RR matching the query, so return it. (Note that this
// includes the case where we were explicitly querying for a CNAME and
// found it. It also includes the case where we were querying for an
// NS RRset and found it at the apex of the zone.)
- return (logAndCreateResult(name, wildname, type, SUCCESS,
- wti->second, lid, flags));
+ return (ResultContext(SUCCESS, wti->second, flags));
}
// If we get here, we have found something at the requested name but not
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index a9870d6..7e3d5c2 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -79,9 +79,12 @@ in the answer as a result.
Debug information. A search in an database data source for NSEC3 that
matches or covers the given name is being started.
-% DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+% DATASRC_DATABASE_FINDNSEC3_COVER found a covering NSEC3 for %1 at label count %2: %3
Debug information. An NSEC3 that covers the given name is found and
-being returned. The found NSEC3 RRset is also displayed.
+being returned. The found NSEC3 RRset is also displayed. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is for a
+superdomain of the given name (see DATASRC_DATABSE_FINDNSEC3_TRYHASH). The
+found NSEC3 RRset is also displayed.
% DATASRC_DATABASE_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
Debug information. An NSEC3 that matches (a possibly superdomain of)
@@ -157,7 +160,7 @@ A search in the database for RRs for the specified name, type and class has
located RRs that match the name and class but not the type. DNSSEC information
has been requested and returned.
-% DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %5
+% DATASRC_DATABASE_FOUND_RRSET search in datasource %1 resulted in RRset %2
The data returned by the database backend contained data for the given domain
name, and it either matches the type or has a relevant type. The RRset that is
returned is printed.
@@ -276,7 +279,7 @@ nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
namespace but has no RRs assopciated with it). This will produce NXRRSET.
-% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %5 with RRset %6
+% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
for a wildcard match, a wildcard record matching the name and type of
the query was found. The data at this point is returned.
diff --git a/src/lib/datasrc/rbnode_rrset.h b/src/lib/datasrc/rbnode_rrset.h
index 3e5d20a..3161cdb 100644
--- a/src/lib/datasrc/rbnode_rrset.h
+++ b/src/lib/datasrc/rbnode_rrset.h
@@ -81,7 +81,7 @@ struct AdditionalNodeInfo;
/// can refer to its definition, and only for that purpose. Otherwise this is
/// essentially a private class of the in-memory data source implementation,
/// and an application shouldn't directly refer to this class.
-///
+///
// Note: non-Doxygen-documented methods are documented in the base class.
class RBNodeRRset : public isc::dns::AbstractRRset {
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 90fb3e4..6c4a937 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -31,9 +31,7 @@ common_sources = run_unittests.cc
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
common_sources += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
-common_ldadd = $(GTEST_LDADD)
-common_ldadd += $(SQLITE_LIBS)
-common_ldadd += $(top_builddir)/src/lib/datasrc/libdatasrc.la
+common_ldadd = $(top_builddir)/src/lib/datasrc/libdatasrc.la
common_ldadd += $(top_builddir)/src/lib/dns/libdns++.la
common_ldadd += $(top_builddir)/src/lib/util/libutil.la
common_ldadd += $(top_builddir)/src/lib/log/liblog.la
@@ -41,6 +39,7 @@ common_ldadd += $(top_builddir)/src/lib/exceptions/libexceptions.la
common_ldadd += $(top_builddir)/src/lib/cc/libcc.la
common_ldadd += $(top_builddir)/src/lib/testutils/libtestutils.la
common_ldadd += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+common_ldadd += $(GTEST_LDADD) $(SQLITE_LIBS)
# The general tests
run_unittests_SOURCES = $(common_sources)
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 1505fbf..824b9e3 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -168,39 +168,48 @@ IfaceMgr::~IfaceMgr() {
}
void IfaceMgr::stubDetectIfaces() {
- string ifaceName, linkLocal;
+ string ifaceName;
+ const string v4addr("127.0.0.1"), v6addr("::1");
// This is a stub implementation for interface detection. Actual detection
- // is faked by reading a text file. It will eventually be removed once
- // we have actual implementations for all supported systems.
+ // is faked by detecting loopback interface (lo or lo0). It will eventually
+ // be removed once we have actual implementations for all supported systems.
- cout << "Interface detection is not implemented yet. "
- << "Reading interfaces.txt file instead." << endl;
- cout << "Please use format: interface-name link-local-address" << endl;
+ cout << "Interface detection is not implemented on this Operating System yet. "
+ << endl;
try {
- ifstream interfaces("interfaces.txt");
-
- if (!interfaces.good()) {
- cout << "interfaces.txt file is not available. Stub interface detection skipped." << endl;
- return;
+ if (if_nametoindex("lo") > 0) {
+ ifaceName = "lo";
+ // this is Linux-like OS
+ } else if (if_nametoindex("lo0") > 0) {
+ ifaceName = "lo0";
+ // this is BSD-like OS
+ } else {
+ // we give up. What OS is this, anyway? Solaris? Hurd?
+ isc_throw(NotImplemented,
+ "Interface detection on this OS is not supported.");
}
- interfaces >> ifaceName;
- interfaces >> linkLocal;
-
- cout << "Detected interface " << ifaceName << "/" << linkLocal << endl;
Iface iface(ifaceName, if_nametoindex(ifaceName.c_str()));
iface.flag_up_ = true;
iface.flag_running_ = true;
+
+ // note that we claim that this is not a loopback. iface_mgr tries to open a
+ // socket on all interaces that are up, running and not loopback. As this is
+ // the only interface we were able to detect, let's pretend this is a normal
+ // interface.
iface.flag_loopback_ = false;
iface.flag_multicast_ = true;
iface.flag_broadcast_ = true;
iface.setHWType(HWTYPE_ETHERNET);
- IOAddress addr(linkLocal);
- iface.addAddress(addr);
+
+ iface.addAddress(IOAddress(v4addr));
+ iface.addAddress(IOAddress(v6addr));
addInterface(iface);
- interfaces.close();
+
+ cout << "Detected interface " << ifaceName << "/" << v4addr << "/"
+ << v6addr << endl;
} catch (const std::exception& ex) {
// TODO: deallocate whatever memory we used
// not that important, since this function is going to be
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index c68de01..cc20bd5 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -2,7 +2,6 @@ SUBDIRS = .
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 83ba231..36104af 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -35,7 +35,6 @@ const size_t buf_size = 32;
char LOOPBACK[buf_size] = "lo";
namespace {
-const char* const INTERFACE_FILE = TEST_DATA_BUILDDIR "/interfaces.txt";
class NakedIfaceMgr: public IfaceMgr {
// "naked" Interface Manager, exposes internal fields
@@ -47,18 +46,11 @@ public:
// dummy class for now, but this will be expanded when needed
class IfaceMgrTest : public ::testing::Test {
public:
+ // these are empty for now, but let's keep them around
IfaceMgrTest() {
}
- void createLoInterfacesTxt() {
- unlink(INTERFACE_FILE);
- fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
- fakeifaces << LOOPBACK << " ::1";
- fakeifaces.close();
- }
-
~IfaceMgrTest() {
- unlink(INTERFACE_FILE);
}
};
@@ -151,9 +143,6 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
TEST_F(IfaceMgrTest, basic) {
// checks that IfaceManager can be instantiated
- createLoInterfacesTxt();
-
- createLoInterfacesTxt();
IfaceMgr & ifacemgr = IfaceMgr::instance();
ASSERT_TRUE(&ifacemgr != 0);
@@ -173,16 +162,17 @@ TEST_F(IfaceMgrTest, ifaceClass) {
// is implemented.
TEST_F(IfaceMgrTest, getIface) {
- createLoInterfacesTxt();
-
cout << "Interface checks. Please ignore socket binding errors." << endl;
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// interface name, ifindex
- IfaceMgr::Iface iface1("lo1", 1);
- IfaceMgr::Iface iface2("eth5", 2);
- IfaceMgr::Iface iface3("en3", 5);
- IfaceMgr::Iface iface4("e1000g0", 3);
+ IfaceMgr::Iface iface1("lo1", 100);
+ IfaceMgr::Iface iface2("eth9", 101);
+ IfaceMgr::Iface iface3("en3", 102);
+ IfaceMgr::Iface iface4("e1000g4", 103);
+ cout << "This test assumes that there are less than 100 network interfaces"
+ << " in the tested system and there are no lo1, eth9, en3, e1000g4"
+ << " or wifi15 interfaces present." << endl;
// note: real interfaces may be detected as well
ifacemgr->getIfacesLst().push_back(iface1);
@@ -200,65 +190,30 @@ TEST_F(IfaceMgrTest, getIface) {
// check that interface can be retrieved by ifindex
- IfaceMgr::Iface* tmp = ifacemgr->getIface(5);
- // ASSERT_NE(NULL, tmp); is not supported. hmmmm.
+ IfaceMgr::Iface* tmp = ifacemgr->getIface(102);
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("en3", tmp->getName());
- EXPECT_EQ(5, tmp->getIndex());
+ EXPECT_EQ(102, tmp->getIndex());
// check that interface can be retrieved by name
tmp = ifacemgr->getIface("lo1");
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("lo1", tmp->getName());
- EXPECT_EQ(1, tmp->getIndex());
+ EXPECT_EQ(100, tmp->getIndex());
// check that non-existing interfaces are not returned
- EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
+ EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi15") );
delete ifacemgr;
}
-#if !defined(OS_LINUX)
-TEST_F(IfaceMgrTest, detectIfaces_stub) {
-
- // test detects that interfaces can be detected
- // there is no code for that now, but interfaces are
- // read from file
- fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
- fakeifaces << "eth0 fe80::1234";
- fakeifaces.close();
-
- // this is not usable on systems that don't have eth0
- // interfaces. Nevertheless, this fake interface should
- // be on list, but if_nametoindex() will fail.
-
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
-
- ASSERT_TRUE(ifacemgr->getIface("eth0") != NULL);
-
- IfaceMgr::Iface* eth0 = ifacemgr->getIface("eth0");
-
- // there should be one address
- IfaceMgr::AddressCollection addrs = eth0->getAddresses();
- ASSERT_EQ(1, addrs.size());
-
- IOAddress addr = *addrs.begin();
-
- EXPECT_STREQ("fe80::1234", addr.toText().c_str());
-
- delete ifacemgr;
-}
-#endif
-
TEST_F(IfaceMgrTest, sockets6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- createLoInterfacesTxt();
-
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IOAddress loAddr("::1");
@@ -320,7 +275,6 @@ TEST_F(IfaceMgrTest, sendReceive6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
@@ -378,7 +332,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
@@ -469,7 +422,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
TEST_F(IfaceMgrTest, socket4) {
- createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// Let's assume that every supported OS have lo interface.
@@ -585,7 +537,6 @@ TEST_F(IfaceMgrTest, socketInfo) {
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
// now let's test if IfaceMgr handles socket info properly
- createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
ASSERT_TRUE(loopback);
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index 0db68c6..a9be8be 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -130,6 +130,11 @@ public:
const RRClass& rrclass, const RRType& rrtype,
const RRTTL& ttl, ConstRdataPtr rdata,
Message::ParseOptions options);
+ // There are also times where an RR needs to be added that
+ // represents an empty RRset. There is no Rdata in that case
+ void addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, Message::ParseOptions options);
void addEDNS(Message::Section section, const Name& name,
const RRClass& rrclass, const RRType& rrtype,
const RRTTL& ttl, const Rdata& rdata);
@@ -561,6 +566,10 @@ Message::removeRRset(const Section section, RRsetIterator& iterator) {
void
Message::clearSection(const Section section) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "clearSection performed in non-render mode");
+ }
if (section >= MessageImpl::NUM_SECTIONS) {
isc_throw(OutOfRange, "Invalid message section: " << section);
}
@@ -736,6 +745,17 @@ MessageImpl::parseSection(const Message::Section section,
const RRClass rrclass(buffer.readUint16());
const RRTTL ttl(buffer.readUint32());
const size_t rdlen = buffer.readUint16();
+
+ // If class is ANY or NONE, rdlength may be zero, to signal
+ // an empty RRset.
+ // (the class check must be done to differentiate from RRTypes
+ // that can have zero length rdata
+ if ((rrclass == RRClass::ANY() || rrclass == RRClass::NONE()) &&
+ rdlen == 0) {
+ addRR(section, name, rrclass, rrtype, ttl, options);
+ ++added;
+ continue;
+ }
ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
if (rrtype == RRType::OPT()) {
@@ -774,6 +794,24 @@ MessageImpl::addRR(Message::Section section, const Name& name,
}
void
+MessageImpl::addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, Message::ParseOptions options)
+{
+ if ((options & Message::PRESERVE_ORDER) == 0) {
+ vector<RRsetPtr>::iterator it =
+ find_if(rrsets_[section].begin(), rrsets_[section].end(),
+ MatchRR(name, rrtype, rrclass));
+ if (it != rrsets_[section].end()) {
+ (*it)->setTTL(min((*it)->getTTL(), ttl));
+ return;
+ }
+ }
+ RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl));
+ rrsets_[section].push_back(rrset);
+}
+
+void
MessageImpl::addEDNS(Message::Section section, const Name& name,
const RRClass& rrclass, const RRType& rrtype,
const RRTTL& ttl, const Rdata& rdata)
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 33551c0..73d0c6e 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -513,6 +513,12 @@ public:
/// \brief Remove all RRSets from the given Section
///
+ /// This method is only allowed in the \c RENDER mode, and the given
+ /// section must be valid.
+ ///
+ /// \throw InvalidMessageOperation Message is not in the \c RENDER mode
+ /// \throw OutOfRange The specified section is not valid
+ ///
/// \param section Section to remove all rrsets from
void clearSection(const Section section);
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index c7ad2ff..fdd4a20 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -76,6 +76,7 @@ PyObject* Message_getSection(PyObject* self, PyObject* args);
PyObject* Message_addQuestion(s_Message* self, PyObject* args);
PyObject* Message_addRRset(s_Message* self, PyObject* args);
PyObject* Message_clear(s_Message* self, PyObject* args);
+PyObject* Message_clearSection(PyObject* pyself, PyObject* args);
PyObject* Message_makeResponse(s_Message* self);
PyObject* Message_toText(s_Message* self);
PyObject* Message_str(PyObject* self);
@@ -149,6 +150,8 @@ PyMethodDef Message_methods[] = {
"Clears the message content (if any) and reinitialize the "
"message in the given mode\n"
"The argument must be either Message.PARSE or Message.RENDER"},
+ { "clear_section", Message_clearSection, METH_VARARGS,
+ Message_clearSection_doc },
{ "make_response", reinterpret_cast<PyCFunction>(Message_makeResponse), METH_NOARGS,
"Prepare for making a response from a request.\n"
"This will clear the DNS header except those fields that should be kept "
@@ -564,6 +567,30 @@ Message_clear(s_Message* self, PyObject* args) {
}
PyObject*
+Message_clearSection(PyObject* pyself, PyObject* args) {
+ s_Message* const self = static_cast<s_Message*>(pyself);
+ int section;
+
+ if (!PyArg_ParseTuple(args, "i", §ion)) {
+ return (NULL);
+ }
+ try {
+ self->cppobj->clearSection(static_cast<Message::Section>(section));
+ Py_RETURN_NONE;
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ return (NULL);
+ } catch (const isc::OutOfRange& ex) {
+ PyErr_SetString(PyExc_OverflowError, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in adding RRset");
+ return (NULL);
+ }
+}
+
+PyObject*
Message_makeResponse(s_Message* self) {
self->cppobj->makeResponse();
Py_RETURN_NONE;
diff --git a/src/lib/dns/python/message_python_inc.cc b/src/lib/dns/python/message_python_inc.cc
index 561c494..e1fd23d 100644
--- a/src/lib/dns/python/message_python_inc.cc
+++ b/src/lib/dns/python/message_python_inc.cc
@@ -38,4 +38,21 @@ Parameters:\n\
options Parse options\n\
\n\
";
+
+const char* const Message_clearSection_doc = "\
+clear_section(section) -> void\n\
+\n\
+Remove all RRSets from the given Section.\n\
+\n\
+This method is only allowed in the RENDER mode, and the given section\n\
+must be valid.\n\
+\n\
+Exceptions:\n\
+ InvalidMessageOperation Message is not in the RENDER mode\n\
+ OverflowError The specified section is not valid\n\
+\n\
+Parameters:\n\
+ section Section to remove all rrsets from\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index ce556df..6758d0e 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -20,6 +20,7 @@
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
+#include <dns/labelsequence.h>
#include "pydnspp_common.h"
#include "messagerenderer_python.h"
@@ -114,6 +115,7 @@ PyObject* Name_reverse(s_Name* self);
PyObject* Name_concatenate(s_Name* self, PyObject* args);
PyObject* Name_downcase(s_Name* self);
PyObject* Name_isWildCard(s_Name* self);
+long Name_hash(PyObject* py_self);
PyMethodDef Name_methods[] = {
{ "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
@@ -518,6 +520,12 @@ Name_isWildCard(s_Name* self) {
}
}
+long
+Name_hash(PyObject* pyself) {
+ s_Name* const self = static_cast<s_Name*>(pyself);
+ return (LabelSequence(*self->cppobj).getHash(false));
+}
+
} // end of unnamed namespace
namespace isc {
@@ -615,7 +623,7 @@ PyTypeObject name_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ Name_hash, // tp_hash
NULL, // tp_call
Name_str, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 64e3cae..23ed463 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -221,6 +221,15 @@ initModulePart_Name(PyObject* mod) {
NameComparisonResult::COMMONANCESTOR, "COMMONANCESTOR");
addClassVariable(name_comparison_result_type, "NameRelation",
po_NameRelation);
+ // Add the constants themselves too
+ addClassVariable(name_comparison_result_type, "SUPERDOMAIN",
+ Py_BuildValue("I", NameComparisonResult::SUPERDOMAIN));
+ addClassVariable(name_comparison_result_type, "SUBDOMAIN",
+ Py_BuildValue("I", NameComparisonResult::SUBDOMAIN));
+ addClassVariable(name_comparison_result_type, "EQUAL",
+ Py_BuildValue("I", NameComparisonResult::EQUAL));
+ addClassVariable(name_comparison_result_type, "COMMONANCESTOR",
+ Py_BuildValue("I", NameComparisonResult::COMMONANCESTOR));
PyModule_AddObject(mod, "NameComparisonResult",
reinterpret_cast<PyObject*>(&name_comparison_result_type));
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index 0014187..2c3dae6 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -52,6 +52,7 @@ PyObject* RRClass_str(PyObject* self);
PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
PyObject* RRClass_getCode(s_RRClass* self);
PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
+long RRClass_hash(PyObject* pyself);
// Static function for direct class creation
PyObject* RRClass_IN(s_RRClass *self);
@@ -264,6 +265,12 @@ PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
+long
+RRClass_hash(PyObject* pyself) {
+ s_RRClass* const self = static_cast<s_RRClass*>(pyself);
+ return (self->cppobj->getCode());
+}
+
} // end anonymous namespace
namespace isc {
@@ -296,7 +303,7 @@ PyTypeObject rrclass_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ RRClass_hash, // tp_hash
NULL, // tp_call
RRClass_str, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 86574fb..1ec0e99 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -289,6 +289,26 @@ class MessageTest(unittest.TestCase):
self.assertRaises(TypeError, self.r.clear, "wrong")
self.assertRaises(TypeError, self.r.clear, 3)
+ def test_clear_question_section(self):
+ self.r.add_question(Question(Name("www.example.com"), RRClass.IN(),
+ RRType.A()))
+ self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
+ self.r.clear_section(Message.SECTION_QUESTION)
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
+
+ def test_clear_section(self):
+ for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,
+ Message.SECTION_ADDITIONAL]:
+ self.r.add_rrset(section, self.rrset_a)
+ self.assertEqual(2, self.r.get_rr_count(section))
+ self.r.clear_section(section)
+ self.assertEqual(0, self.r.get_rr_count(section))
+
+ self.assertRaises(InvalidMessageOperation, self.p.clear_section,
+ Message.SECTION_ANSWER)
+ self.assertRaises(OverflowError, self.r.clear_section,
+ self.bogus_section)
+
def test_to_wire(self):
self.assertRaises(TypeError, self.r.to_wire, 1)
self.assertRaises(InvalidMessageOperation,
diff --git a/src/lib/dns/python/tests/name_python_test.py b/src/lib/dns/python/tests/name_python_test.py
index 5263412..8ea2e35 100644
--- a/src/lib/dns/python/tests/name_python_test.py
+++ b/src/lib/dns/python/tests/name_python_test.py
@@ -218,5 +218,27 @@ class NameTest(unittest.TestCase):
self.assertTrue(self.name4 <= self.name1)
self.assertFalse(self.name2 >= self.name1)
+ def test_hash(self):
+ # The same name should have the same hash value.
+ self.assertEqual(hash(Name('example.com')), hash(Name('example.com')))
+ # Hash is case insensitive.
+ self.assertEqual(hash(Name('example.com')), hash(Name('EXAMPLE.COM')))
+
+ # These pairs happen to be known to have different hashes.
+ # It may be naive to assume the hash value is always the same (we use
+ # an external library and it depends on its internal details). If
+ # it turns out that this assumption isn't always held, we should
+ # disable this test.
+ self.assertNotEqual(hash(Name('example.com')),
+ hash(Name('example.org')))
+
+ # Check insensitiveness for the case of inequality.
+ # Based on the assumption above, this 'if' should be true and
+ # we'll always test the case inside it. We'll still keep the if in
+ # case we end up disabling the above test.
+ if hash(Name('example.com')) != hash(Name('example.org')):
+ self.assertNotEqual(hash(Name('example.com')),
+ hash(Name('EXAMPLE.ORG')))
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/rrclass_python_test.py b/src/lib/dns/python/tests/rrclass_python_test.py
index 38d8c8c..a048c4c 100644
--- a/src/lib/dns/python/tests/rrclass_python_test.py
+++ b/src/lib/dns/python/tests/rrclass_python_test.py
@@ -78,6 +78,14 @@ class RRClassTest(unittest.TestCase):
self.assertTrue(self.c1 <= self.c2)
self.assertFalse(self.c1 != other_rrclass)
+ def test_hash(self):
+ # Exploiting the knowledge that the hash value is the numeric class
+ # value, we can predict the comparison result.
+ self.assertEqual(hash(RRClass.IN()), hash(RRClass("IN")))
+ self.assertEqual(hash(RRClass("in")), hash(RRClass("IN")))
+ self.assertNotEqual(hash(RRClass.IN()), hash(RRClass.CH()))
+ self.assertNotEqual(hash(RRClass.IN()), hash(RRClass("CLASS65535")))
+
def test_statics(self):
self.assertEqual(RRClass.IN(), RRClass("IN"))
self.assertEqual(RRClass.CH(), RRClass("CH"))
diff --git a/src/lib/dns/python/tests/rrset_python_test.py b/src/lib/dns/python/tests/rrset_python_test.py
index de475a7..0544872 100644
--- a/src/lib/dns/python/tests/rrset_python_test.py
+++ b/src/lib/dns/python/tests/rrset_python_test.py
@@ -30,6 +30,7 @@ class TestModuleSpec(unittest.TestCase):
self.test_nsname = Name("ns.example.com")
self.rrset_a = RRset(self.test_name, RRClass("IN"), RRType("A"), RRTTL(3600))
self.rrset_a_empty = RRset(self.test_name, RRClass("IN"), RRType("A"), RRTTL(3600))
+ self.rrset_any_a_empty = RRset(self.test_name, RRClass("ANY"), RRType("A"), RRTTL(3600))
self.rrset_ns = RRset(self.test_domain, RRClass("IN"), RRType("NS"), RRTTL(86400))
self.rrset_ch_txt = RRset(self.test_domain, RRClass("CH"), RRType("TXT"), RRTTL(0))
self.MAX_RDATA_COUNT = 100
@@ -90,6 +91,9 @@ class TestModuleSpec(unittest.TestCase):
self.assertRaises(EmptyRRset, self.rrset_a_empty.to_text)
+ self.assertEqual("test.example.com. 3600 ANY A\n",
+ self.rrset_any_a_empty.to_text())
+
def test_to_wire_buffer(self):
exp_buffer = bytearray(b'\x04test\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x01\x04test\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x02')
buffer = bytearray()
@@ -99,6 +103,11 @@ class TestModuleSpec(unittest.TestCase):
self.assertRaises(EmptyRRset, self.rrset_a_empty.to_wire, buffer);
self.assertRaises(TypeError, self.rrset_a.to_wire, 1)
+ exp_buffer = bytearray(b'\x04test\x07example\x03com\x00\x00\x01\x00\xff\x00\x00\x0e\x10\x00\x00')
+ buffer = bytearray()
+ self.rrset_any_a_empty.to_wire(buffer)
+ self.assertEqual(exp_buffer, buffer)
+
def test_to_wire_renderer(self):
exp_buffer = bytearray(b'\x04test\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x01\xc0\x00\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04\xc0\x00\x02\x02')
mr = MessageRenderer()
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
index a7a0bb4..6eea2c7 100644
--- a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
@@ -127,4 +127,3 @@ parseNSEC3ParamWire(const char* const rrtype_name,
} // end of rdata
} // end of dns
} // end of isc
-
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
index 9a55f5f..4d8e262 100644
--- a/src/lib/dns/rrset.cc
+++ b/src/lib/dns/rrset.cc
@@ -43,14 +43,24 @@ AbstractRRset::toText() const {
string s;
RdataIteratorPtr it = getRdataIterator();
+ // In the case of an empty rrset, just print name, ttl, class, and
+ // type
if (it->isLast()) {
- isc_throw(EmptyRRset, "ToText() is attempted for an empty RRset");
+ // But only for class ANY or NONE
+ if (getClass() != RRClass::ANY() &&
+ getClass() != RRClass::NONE()) {
+ isc_throw(EmptyRRset, "toText() is attempted for an empty RRset");
+ }
+
+ s += getName().toText() + " " + getTTL().toText() + " " +
+ getClass().toText() + " " + getType().toText() + "\n";
+ return (s);
}
do {
s += getName().toText() + " " + getTTL().toText() + " " +
- getClass().toText() + " " + getType().toText() + " " +
- it->getCurrent().toText() + "\n";
+ getClass().toText() + " " + getType().toText() + " " +
+ it->getCurrent().toText() + "\n";
it->next();
} while (!it->isLast());
@@ -65,7 +75,21 @@ rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
RdataIteratorPtr it = rrset.getRdataIterator();
if (it->isLast()) {
- isc_throw(EmptyRRset, "ToWire() is attempted for an empty RRset");
+ // empty rrsets are only allowed for classes ANY and NONE
+ if (rrset.getClass() != RRClass::ANY() &&
+ rrset.getClass() != 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.
+ rrset.getName().toWire(output);
+ rrset.getType().toWire(output);
+ rrset.getClass().toWire(output);
+ rrset.getTTL().toWire(output);
+ output.writeUint16(0);
+ // Still counts as 1 'rr'; it does show up in the message
+ return (1);
}
// sort the set of Rdata based on rrset-order and sortlist, and possible
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index 43ade58..74380ce 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -267,8 +267,8 @@ public:
/// the resulting string with a trailing newline character.
/// (following the BIND9 convention)
///
- /// The RRset must contain some RDATA; otherwise, an exception of class
- /// \c EmptyRRset will be thrown.
+ /// If the class is not ANY or NONE, the RRset must contain some RDATA;
+ /// otherwise, an exception of class \c EmptyRRset will be thrown.
/// If resource allocation fails, a corresponding standard exception
/// will be thrown.
/// The default implementation may throw other exceptions if the
@@ -299,8 +299,8 @@ public:
///
/// If resource allocation fails, a corresponding standard exception
/// will be thrown.
- /// The RRset must contain some RDATA; otherwise, an exception of class
- /// \c EmptyRRset will be thrown.
+ /// If the class is not ANY or NONE, the RRset must contain some RDATA;
+ /// otherwise, an exception of class \c EmptyRRset will be thrown.
/// The default implementation may throw other exceptions if the
/// \c toWire() method of the RDATA objects throws.
/// If a derived class of \c AbstractRRset overrides the default
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 26b4630..6057828 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -71,11 +71,11 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
# an older version of botan, and somehow that version gets
# linked if we don't
run_unittests_LDFLAGS = $(BOTAN_LDFLAGS) $(GTEST_LDFLAGS) $(AM_LDFLAGS)
-run_unittests_LDADD = $(BOTAN_LIBS) $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(BOTAN_LIBS) $(GTEST_LDADD)
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 3be1436..c5dd3ed 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -466,6 +466,13 @@ TEST_F(MessageTest, clearAdditionalSection) {
EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
}
+TEST_F(MessageTest, badClearSection) {
+ // attempt of clearing a message in the parse mode.
+ EXPECT_THROW(message_parse.clearSection(Message::SECTION_QUESTION),
+ InvalidMessageOperation);
+ // attempt of clearing out-of-range section
+ EXPECT_THROW(message_render.clearSection(bogus_section), OutOfRange);
+}
TEST_F(MessageTest, badBeginSection) {
// valid cases are tested via other tests
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index 1603293..c375579 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -46,6 +46,10 @@ protected:
rrset_a(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)),
rrset_a_empty(test_name, RRClass::IN(), RRType::A(),
RRTTL(3600)),
+ rrset_any_a_empty(test_name, RRClass::ANY(), RRType::A(),
+ RRTTL(3600)),
+ rrset_none_a_empty(test_name, RRClass::NONE(), RRType::A(),
+ RRTTL(3600)),
rrset_ns(test_domain, RRClass::IN(), RRType::NS(),
RRTTL(86400)),
rrset_ch_txt(test_domain, RRClass::CH(), RRType::TXT(),
@@ -62,6 +66,8 @@ protected:
Name test_nsname;
RRset rrset_a;
RRset rrset_a_empty;
+ RRset rrset_any_a_empty;
+ RRset rrset_none_a_empty;
RRset rrset_ns;
RRset rrset_ch_txt;
std::vector<unsigned char> wiredata;
@@ -193,8 +199,14 @@ TEST_F(RRsetTest, toText) {
"test.example.com. 3600 IN A 192.0.2.2\n",
rrset_a.toText());
- // toText() cannot be performed for an empty RRset.
+ // toText() cannot be performed for an empty RRset
EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset);
+
+ // Unless it is type ANY or NONE
+ EXPECT_EQ("test.example.com. 3600 ANY A\n",
+ rrset_any_a_empty.toText());
+ EXPECT_EQ("test.example.com. 3600 CLASS254 A\n",
+ rrset_none_a_empty.toText());
}
TEST_F(RRsetTest, toWireBuffer) {
@@ -207,6 +219,20 @@ TEST_F(RRsetTest, toWireBuffer) {
// toWire() cannot be performed for an empty RRset.
buffer.clear();
EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset);
+
+ // Unless it is type ANY or None
+ buffer.clear();
+ rrset_any_a_empty.toWire(buffer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire3", wiredata);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+ buffer.getLength(), &wiredata[0], wiredata.size());
+ buffer.clear();
+ rrset_none_a_empty.toWire(buffer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire4", wiredata);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+ buffer.getLength(), &wiredata[0], wiredata.size());
}
TEST_F(RRsetTest, toWireRenderer) {
@@ -220,8 +246,24 @@ TEST_F(RRsetTest, toWireRenderer) {
renderer.getLength(), &wiredata[0], wiredata.size());
// toWire() cannot be performed for an empty RRset.
- renderer.clear();
- EXPECT_THROW(rrset_a_empty.toWire(renderer), EmptyRRset);
+ buffer.clear();
+ EXPECT_THROW(rrset_a_empty.toWire(buffer), 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);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire3", wiredata);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+ buffer.getLength(), &wiredata[0], wiredata.size());
+
+ buffer.clear();
+ rrset_none_a_empty.toWire(buffer);
+ wiredata.clear();
+ UnitTestUtil::readWireData("rrset_toWire4", wiredata);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(),
+ buffer.getLength(), &wiredata[0], wiredata.size());
}
// test operator<<. We simply confirm it appends the result of toText().
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index f450a59..fb1ec5b 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -146,6 +146,7 @@ EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire
EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
EXTRA_DIST += rrset_toWire1 rrset_toWire2
+EXTRA_DIST += rrset_toWire3 rrset_toWire4
EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec
diff --git a/src/lib/dns/tests/testdata/rrset_toWire3 b/src/lib/dns/tests/testdata/rrset_toWire3
new file mode 100644
index 0000000..47f8e6b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire3
@@ -0,0 +1,12 @@
+#
+# Rendering an empty IN/A RRset
+#
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, ANY = 255
+00 01 00 ff
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 0
+00 00
diff --git a/src/lib/dns/tests/testdata/rrset_toWire4 b/src/lib/dns/tests/testdata/rrset_toWire4
new file mode 100644
index 0000000..6fb409c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rrset_toWire4
@@ -0,0 +1,12 @@
+#
+# Rendering an empty IN/A RRset
+#
+#(4) t e s t (7) e x a m p l e (3) c o m .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# type/class: A = 1, ANY = 255
+00 01 00 fe
+# TTL: 3600
+00 00 0e 10
+#6 7
+# RDLENGTH: 0
+00 00
diff --git a/src/lib/log/log_formatter.cc b/src/lib/log/log_formatter.cc
index c728cb5..9cd5cc7 100644
--- a/src/lib/log/log_formatter.cc
+++ b/src/lib/log/log_formatter.cc
@@ -17,6 +17,10 @@
#include <cassert>
+#ifdef ENABLE_LOGGER_CHECKS
+#include <iostream>
+#endif
+
using namespace std;
using namespace boost;
@@ -59,6 +63,9 @@ checkExcessPlaceholders(string* message, unsigned int placeholder) {
// but we can't at least for now because this function is called from
// the Formatter's destructor.
#ifdef ENABLE_LOGGER_CHECKS
+ // Also, make sure we print the message so we can identify which
+ // identifier has the problem.
+ cerr << "Message " << *message << endl;
assert("Excess logger placeholders still exist in message" == NULL);
#else
message->append(" @@Excess logger placeholders still exist@@");
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
index fc60203..eebdb1a 100644
--- a/src/lib/log/log_formatter.h
+++ b/src/lib/log/log_formatter.h
@@ -197,7 +197,9 @@ public:
try {
return (arg(boost::lexical_cast<std::string>(value)));
} catch (const boost::bad_lexical_cast& ex) {
-
+ // The formatting of the log message got wrong, we don't want
+ // to output it.
+ deactivate();
// A bad_lexical_cast during a conversion to a string is
// *extremely* unlikely to fail. However, there is nothing
// in the documentation that rules it out, so we need to handle
@@ -229,10 +231,35 @@ public:
// occurrences of "%2" with 42. (Conversely, the sequence
// .arg(42).arg("%1") would return "42 %1" - there are no recursive
// replacements).
- replacePlaceholder(message_, arg, ++nextPlaceholder_ );
+ try {
+ replacePlaceholder(message_, arg, ++nextPlaceholder_ );
+ }
+ catch (...) {
+ // Something went wrong here, the log message is broken, so
+ // we don't want to output it, nor we want to check all the
+ // placeholders were used (because they won't be).
+ deactivate();
+ throw;
+ }
}
return (*this);
}
+
+ /// \brief Turn off the output of this logger.
+ ///
+ /// If the logger would output anything at the end, now it won't.
+ /// Also, this turns off the strict checking of placeholders, if
+ /// it is compiled in.
+ ///
+ /// The expected use is when there was an exception processing
+ /// the arguments for the message.
+ void deactivate() {
+ if (logger_) {
+ delete message_;
+ message_ = NULL;
+ logger_ = NULL;
+ }
+ }
};
}
diff --git a/src/lib/log/tests/log_formatter_unittest.cc b/src/lib/log/tests/log_formatter_unittest.cc
index 83fc062..435b200 100644
--- a/src/lib/log/tests/log_formatter_unittest.cc
+++ b/src/lib/log/tests/log_formatter_unittest.cc
@@ -81,6 +81,14 @@ TEST_F(FormatterTest, stringArg) {
}
}
+// Test the .deactivate() method
+TEST_F(FormatterTest, deactivate) {
+ Formatter(isc::log::INFO, s("Text of message"), this).deactivate();
+ // If there was no .deactivate, it should have output it.
+ // But not now.
+ ASSERT_EQ(0, outputs.size());
+}
+
// Can convert to string
TEST_F(FormatterTest, intArg) {
Formatter(isc::log::INFO, s("The answer is %1"), this).arg(42);
@@ -117,15 +125,12 @@ TEST_F(FormatterTest, mismatchedPlaceholders) {
arg("only one");
}, ".*");
- // Mixed case of above two: the exception will be thrown due to the missing
- // placeholder, but before even it's caught the program will be aborted
- // due to the unused placeholder as a result of the exception.
- EXPECT_DEATH({
- isc::util::unittests::dontCreateCoreDumps();
- Formatter(isc::log::INFO, s("Missing the first %2"), this).
- arg("missing").arg("argument");
- }, ".*");
#endif /* EXPECT_DEATH */
+ // Mixed case of above two: the exception will be thrown due to the missing
+ // placeholder. The other check is disabled due to that.
+ EXPECT_THROW(Formatter(isc::log::INFO, s("Missing the first %2"), this).
+ arg("missing").arg("argument"),
+ isc::log::MismatchedPlaceholders);
}
#else
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index aef5dc3..80fd222 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages server_common
+SUBDIRS += xfrin log_messages server_common ddns
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 23e627e..aa0547b 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -167,7 +167,7 @@ class ConfigManagerData:
i += 1
new_file_name = new_file_name + "." + str(i)
if os.path.exists(old_file_name):
- logger.info(CFGMGR_RENAMED_CONFIG_FILE, old_file_name, new_file_name)
+ logger.info(CFGMGR_BACKED_UP_CONFIG_FILE, old_file_name, new_file_name)
os.rename(old_file_name, new_file_name)
def __eq__(self, other):
diff --git a/src/lib/python/isc/config/cfgmgr_messages.mes b/src/lib/python/isc/config/cfgmgr_messages.mes
index e608594..e6cb352 100644
--- a/src/lib/python/isc/config/cfgmgr_messages.mes
+++ b/src/lib/python/isc/config/cfgmgr_messages.mes
@@ -55,10 +55,11 @@ error is given. The most likely cause is that the system does not have
write access to the configuration database file. The updated
configuration is not stored.
-% CFGMGR_RENAMED_CONFIG_FILE renamed configuration file %1 to %2, will create new %1
-BIND 10 has been started with the command to clear the configuration file.
-The existing file is backed up to the given file name, so that data is not
-immediately lost if this was done by accident.
+% CFGMGR_BACKED_UP_CONFIG_FILE Config file %1 was removed; a backup was made at %2
+BIND 10 has been started with the command to clear the configuration
+file. The existing file has been backed up (moved) to the given file
+name. A new configuration file will be created in the original location
+when necessary.
% CFGMGR_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
There was a keyboard interrupt signal to stop the cfgmgr daemon. The
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index ed05fdb..fb00f5f 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -147,7 +147,8 @@ PyObject* ZoneFinder_helper_all(ZoneFinder* finder, PyObject* args) {
// increases the refcount and the container decreases it
// later. This way, it feels safer in case the build function
// would fail.
- return (Py_BuildValue("IO", r, list_container.get()));
+ return (Py_BuildValue("IOI", r, list_container.get(),
+ result_flags));
} else {
if (rrsp) {
// Use N instead of O so the refcount isn't increased twice
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index c7bf6b4..aa61185 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -83,7 +83,7 @@ def test_findall_common(self, tested):
# A success. It should return the list now.
# This also tests we can ommit the options parameter
- result, rrsets = tested.find_all(isc.dns.Name("mix.example.com."))
+ result, rrsets, _ = tested.find_all(isc.dns.Name("mix.example.com."))
self.assertEqual(ZoneFinder.SUCCESS, result)
self.assertEqual(2, len(rrsets))
rrsets.sort(key=lambda rrset: rrset.get_type().to_text())
diff --git a/src/lib/python/isc/ddns/Makefile.am b/src/lib/python/isc/ddns/Makefile.am
new file mode 100644
index 0000000..1b9b6df
--- /dev/null
+++ b/src/lib/python/isc/ddns/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py session.py logger.py zone_config.py
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+EXTRA_DIST = libddns_messages.mes
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.pyc
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py: libddns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libddns_messages.mes
+
+pythondir = $(pyexecdir)/isc/ddns
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/ddns/__init__.py b/src/lib/python/isc/ddns/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
new file mode 100644
index 0000000..12c3a8c
--- /dev/null
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -0,0 +1,142 @@
+# 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.
+
+# No namespace declaration - these constants go in the global namespace
+# of the libddns_messages python module.
+
+% LIBDDNS_PREREQ_FORMERR update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it has a non-zero TTL value.
+A FORMERR error response is sent to the client.
+
+% LIBDDNS_PREREQ_FORMERR_ANY update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
+% LIBDDNS_PREREQ_FORMERR_CLASS update client %1 for zone %2: Format error in prerequisite (%3). Bad class.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, the class of the
+prerequisite should either match the class of the zone in the Zone Section,
+or it should be ANY or NONE, and it is not. A FORMERR error response is sent
+to the client.
+
+% LIBDDNS_PREREQ_FORMERR_NONE update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found.
+The prerequisite with the given name, class and type is not well-formed.
+The specific prerequisite is shown. In this case, it either has a non-zero
+TTL value, or has rdata fields. A FORMERR error response is sent to the client.
+
+% LIBDDNS_PREREQ_NAME_IN_USE_FAILED update client %1 for zone %2: 'Name is in use' prerequisite not satisfied (%3), rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is in use'. From RFC2136:
+Name is in use. At least one RR with a specified NAME (in
+the zone and class specified by the Zone Section) must exist.
+Note that this prerequisite is NOT satisfied by empty
+nonterminals.
+
+% LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED update client %1 for zone %2: 'Name is not in use' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'Name is not in use'.
+From RFC2136:
+Name is not in use. No RR of any type is owned by a
+specified NAME. Note that this prerequisite IS satisfied by
+empty nonterminals.
+
+% LIBDDNS_PREREQ_NOTZONE update client %1 for zone %2: prerequisite not in zone (%3)
+A DNS UPDATE prerequisite has a name that does not appear to be inside
+the zone specified in the Zone section of the UPDATE message.
+The specific prerequisite is shown. A NOTZONE error response is sent to
+the client.
+
+% LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED update client %1 for zone %2: 'RRset does not exist' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset does not exist'.
+From RFC2136:
+RRset does not exist. No RRs with a specified NAME and TYPE
+(in the zone and class denoted by the Zone Section) can exist.
+
+% LIBDDNS_PREREQ_RRSET_EXISTS_FAILED update client %1 for zone %2: 'RRset exists (value independent)' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value independent)'.
+From RFC2136:
+RRset exists (value dependent). A set of RRs with a
+specified NAME and TYPE exists and has the same members
+with the same RDATAs as the RRset specified here in this
+Section.
+
+% LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED update client %1 for zone %2: 'RRset exists (value dependent)' (%3) prerequisite not satisfied, rcode: %4
+A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that
+was not satisfied is shown. The client is sent an error response with the
+given rcode.
+In this case, the specific prerequisite is 'RRset exists (value dependent)'.
+From RFC2136:
+RRset exists (value independent). At least one RR with a
+specified NAME and TYPE (in the zone and class specified by
+the Zone Section) must exist.
+
+% LIBDDNS_UPDATE_APPROVED update client %1 for zone %2 approved
+Debug message. An update request was approved in terms of the zone's
+update ACL.
+
+% LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will respond to the request with an RCODE of
+REFUSED as described in Section 3.3 of RFC2136.
+
+% LIBDDNS_UPDATE_DROPPED update client %1 for zone %2 dropped
+Informational message. An update request was denied because it was
+rejected by the zone's update ACL. When this library is used by
+b10-ddns, the server will then completely ignore the request; no
+response will be sent.
+
+% LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3
+Debug message. An error is found in processing a dynamic update
+request. This log message is used for general errors that are not
+normally expected to happen. So, in general, it would mean some
+problem in the client implementation or an interoperability issue
+with this implementation. The client's address, the zone name and
+class, and description of the error are logged.
+
+% LIBDDNS_UPDATE_FORWARD_FAIL update client %1 for zone %2: update forwarding not supported
+Debug message. An update request is sent to a secondary server. This
+is not necessarily invalid, but this implementation does not yet
+support update forwarding as specified in Section 6 of RFC2136 and it
+will simply return a response with an RCODE of NOTIMP to the client.
+The client's address and the zone name/class are logged.
+
+% LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone
+Debug message. An update request was received for a zone for which
+the receiving server doesn't have authority. In theory this is an
+unexpected event, but there are client implementations that could send
+update requests carelessly, so it may not necessarily be so uncommon
+in practice. If possible, you may want to check the implementation or
+configuration of those clients to suppress the requests. As specified
+in Section 3.1 of RFC2136, the receiving server will return a response
+with an RCODE of NOTAUTH.
+
+% LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update update client %1 for zone %2: result code %3
+The handling of the prerequisite section (RFC2136 Section 3.2) found
+that one of the prerequisites was not satisfied. The result code
+should give more information on what prerequisite type failed.
+If the result code is FORMERR, the prerequisite section was not well-formed.
+An error response with the given result code is sent back to the client.
diff --git a/src/lib/python/isc/ddns/logger.py b/src/lib/python/isc/ddns/logger.py
new file mode 100644
index 0000000..0f95bd7
--- /dev/null
+++ b/src/lib/python/isc/ddns/logger.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+""" This is a logging utility module for other modules of the ddns library
+package.
+
+"""
+
+import isc.log
+
+# The logger for this package
+logger = isc.log.Logger('libddns')
+
+class ClientFormatter:
+ """A utility class to convert a client address to string.
+
+ This class is constructed with a Python standard socket address tuple.
+ If it's 2-element tuple, it's assumed to be an IPv4 socket address
+ and will be converted to the form of '<addr>:<port>(/key=<tsig-key>)'.
+ If it's 4-element tuple, it's assumed to be an IPv6 socket address.
+ and will be converted to the form of '[<addr>]:<por>(/key=<tsig-key>)'.
+ The optional key=<tsig-key> will be added if a TSIG record is given
+ on construction. tsig-key is the TSIG key name in that case.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ Note: this optimization comes with the cost of instantiating the
+ formatter object itself. It's not really clear which overhead is
+ heavier, and we may conclude it's actually better to just generate
+ the strings unconditionally. Alternatively, we can make the stored
+ address of this object replaceable so that this object can be reused.
+ Right now this is an open issue.
+
+ """
+ def __init__(self, addr, tsig_record=None):
+ self.__addr = addr
+ self.__tsig_record = tsig_record
+
+ def __format_addr(self):
+ if len(self.__addr) == 2:
+ return self.__addr[0] + ':' + str(self.__addr[1])
+ elif len(self.__addr) == 4:
+ return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
+ return None
+
+ def __str__(self):
+ format = self.__format_addr()
+ if format is not None and self.__tsig_record is not None:
+ format += '/key=' + self.__tsig_record.get_name().to_text(True)
+ return format
+
+class ZoneFormatter:
+ """A utility class to convert zone name and class to string.
+
+ This class is constructed with a name of a zone (isc.dns.Name object)
+ and its RR class (isc.dns.RRClass object). Its text conversion method
+ (__str__) converts them into a string in the form of
+ '<zone name>/<zone class>' where the trailing dot of the zone name
+ is omitted.
+
+ If the given zone name on construction is None, it's assumed to be
+ the zone isn't identified but needs to be somehow logged. The conversion
+ method returns a special string to indicate this case.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ See the note for the ClientFormatter class about overhead tradeoff.
+ This class shares the same discussion.
+
+ """
+ def __init__(self, zname, zclass):
+ self.__zname = zname
+ self.__zclass = zclass
+
+ def __str__(self):
+ if self.__zname is None:
+ return '(zone unknown/not determined)'
+ return self.__zname.to_text(True) + '/' + self.__zclass.to_text()
+
+class RRsetFormatter:
+ """A utility class to convert rrsets to a short descriptive string.
+
+ This class is constructed with an rrset (isc.dns.RRset object).
+ Its text conversion method (__str__) converts it into a string
+ with only the name, class and type of the rrset.
+ This is used in logging so that the RRset can be identified, without
+ being completely printed, which would result in an unnecessary
+ multi-line message.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level.
+
+ See the note for the ClientFormatter class about overhead tradeoff.
+ This class shares the same discussion.
+ """
+ def __init__(self, rrset):
+ self.__rrset = rrset
+
+ def __str__(self):
+ return self.__rrset.get_name().to_text() + " " +\
+ self.__rrset.get_class().to_text() + " " +\
+ self.__rrset.get_type().to_text()
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
new file mode 100644
index 0000000..6a4079d
--- /dev/null
+++ b/src/lib/python/isc/ddns/session.py
@@ -0,0 +1,395 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+from isc.dns import *
+import isc.ddns.zone_config
+from isc.log import *
+from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter,\
+ RRsetFormatter
+from isc.log_messages.libddns_messages import *
+from isc.acl.acl import ACCEPT, REJECT, DROP
+import copy
+
+# Result codes for UpdateSession.handle()
+UPDATE_SUCCESS = 0
+UPDATE_ERROR = 1
+UPDATE_DROP = 2
+
+# Convenient aliases of update-specific section names
+SECTION_ZONE = Message.SECTION_QUESTION
+SECTION_PREREQUISITE = Message.SECTION_ANSWER
+SECTION_UPDATE = Message.SECTION_AUTHORITY
+
+# Shortcut
+DBGLVL_TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+
+class UpdateError(Exception):
+ '''Exception for general error in update request handling.
+
+ This exception is intended to be used internally within this module.
+ When UpdateSession.handle() encounters an error in handling an update
+ request it can raise this exception to terminate the handling.
+
+ This class is constructed with some information that may be useful for
+ subsequent possible logging:
+ - msg (string) A string explaining the error.
+ - zname (isc.dns.Name) The zone name. Can be None when not identified.
+ - zclass (isc.dns.RRClass) The zone class. Like zname, can be None.
+ - rcode (isc.dns.RCode or None) The RCODE to be set in the response
+ message; this can be None if the response is not expected to be sent.
+ - nolog (bool) If True, it indicates there's no more need for logging.
+
+ '''
+ def __init__(self, msg, zname, zclass, rcode, nolog=False):
+ Exception.__init__(self, msg)
+ self.zname = zname
+ self.zclass = zclass
+ self.rcode = rcode
+ self.nolog = nolog
+
+class UpdateSession:
+ '''Protocol handling for a single dynamic update request.
+
+ This class is instantiated with a request message and some other
+ information that will be used for handling the request. Its main
+ method, handle(), will process the request, and normally build
+ a response message according to the result. The application of this
+ class can use the message to send a response to the client.
+
+ '''
+ def __init__(self, req_message, client_addr, zone_config):
+ '''Constructor.
+
+ Parameters:
+ - req_message (isc.dns.Message) The request message. This must be
+ in the PARSE mode, its Opcode must be UPDATE, and must have been
+ TSIG validatd if it's TSIG signed.
+ - client_addr (socket address) The address/port of the update client
+ in the form of Python socket address object. This is mainly for
+ logging and access control.
+ - zone_config (ZoneConfig) A tentative container that encapsulates
+ the server's zone configuration. See zone_config.py.
+ - req_data (binary) Wire format data of the request message.
+ It will be used for TSIG verification if necessary.
+
+ '''
+ self.__message = req_message
+ self.__tsig = req_message.get_tsig_record()
+ self.__client_addr = client_addr
+ self.__zone_config = zone_config
+
+ def get_message(self):
+ '''Return the update message.
+
+ After handle() is called, it's generally transformed to the response
+ to be returned to the client. If the request has been dropped,
+ this method returns None. If this method is called before handle()
+ the return value would be identical to the request message passed on
+ construction, although it's of no practical use.
+
+ '''
+ return self.__message
+
+ def handle(self):
+ '''Handle the update request according to RFC2136.
+
+ This method returns a tuple of the following three elements that
+ indicate the result of the request.
+ - Result code of the request processing, which are:
+ UPDATE_SUCCESS Update request granted and succeeded.
+ UPDATE_ERROR Some error happened to be reported in the response.
+ UPDATE_DROP Error happened and no response should be sent.
+ Except the case of UPDATE_DROP, the UpdateSession object will have
+ created a response that is to be returned to the request client,
+ which can be retrieved by get_message(). If it's UPDATE_DROP,
+ subsequent call to get_message() returns None.
+ - The name of the updated zone (isc.dns.Name object) in case of
+ UPDATE_SUCCESS; otherwise None.
+ - The RR class of the updated zone (isc.dns.RRClass object) in case
+ of UPDATE_SUCCESS; otherwise None.
+
+ '''
+ try:
+ datasrc_client, zname, zclass = self.__get_update_zone()
+ # conceptual code that would follow
+ prereq_result = self.__check_prerequisites(datasrc_client,
+ zname, zclass)
+ if prereq_result != Rcode.NOERROR():
+ self.__make_response(prereq_result)
+ return UPDATE_ERROR, zname, zclass
+ self.__check_update_acl(zname, zclass)
+ # self.__do_update()
+ # self.__make_response(Rcode.NOERROR())
+ return UPDATE_SUCCESS, zname, zclass
+ except UpdateError as e:
+ if not e.nolog:
+ logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(e.zname, e.zclass), e)
+ # If RCODE is specified, create a corresponding resonse and return
+ # ERROR; otherwise clear the message and return DROP.
+ if e.rcode is not None:
+ self.__make_response(e.rcode)
+ return UPDATE_ERROR, None, None
+ self.__message = None
+ return UPDATE_DROP, None, None
+
+ def __get_update_zone(self):
+ '''Parse the zone section and find the zone to be updated.
+
+ If the zone section is valid and the specified zone is found in
+ the configuration, it returns a tuple of:
+ - A matching data source that contains the specified zone
+ - The zone name as a Name object
+ - The zone class as an RRClass object
+
+ '''
+ # Validation: the zone section must contain exactly one question,
+ # and it must be of type SOA.
+ n_zones = self.__message.get_rr_count(SECTION_ZONE)
+ if n_zones != 1:
+ raise UpdateError('Invalid number of records in zone section: ' +
+ str(n_zones), None, None, Rcode.FORMERR())
+ zrecord = self.__message.get_question()[0]
+ if zrecord.get_type() != RRType.SOA():
+ raise UpdateError('update zone section contains non-SOA',
+ None, None, Rcode.FORMERR())
+
+ # See if we're serving a primary zone specified in the zone section.
+ zname = zrecord.get_name()
+ zclass = zrecord.get_class()
+ zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
+ if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
+ return datasrc_client, zname, zclass
+ elif zone_type == isc.ddns.zone_config.ZONE_SECONDARY:
+ # We are a secondary server; since we don't yet support update
+ # forwarding, we return 'not implemented'.
+ logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_FORWARD_FAIL,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('forward', zname, zclass, Rcode.NOTIMP(), True)
+ # zone wasn't found
+ logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_NOTAUTH,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
+
+ def __check_update_acl(self, zname, zclass):
+ '''Apply update ACL for the zone to be updated.'''
+ acl = self.__zone_config.get_update_acl(zname, zclass)
+ action = acl.execute(isc.acl.dns.RequestContext(
+ (self.__client_addr[0], self.__client_addr[1]), self.__tsig))
+ if action == REJECT:
+ logger.info(LIBDDNS_UPDATE_DENIED,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('rejected', zname, zclass, Rcode.REFUSED(), True)
+ if action == DROP:
+ logger.info(LIBDDNS_UPDATE_DROPPED,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('dropped', zname, zclass, None, True)
+ logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_APPROVED,
+ ClientFormatter(self.__client_addr, self.__tsig),
+ ZoneFormatter(zname, zclass))
+
+ def __make_response(self, rcode):
+ '''Transform the internal message to the update response.
+
+ According RFC2136 Section 3.8, the zone section will be cleared
+ as well as other sections. The response Rcode will be set to the
+ given value.
+
+ '''
+ self.__message.make_response()
+ self.__message.clear_section(SECTION_ZONE)
+ self.__message.set_rcode(rcode)
+
+ def __prereq_rrset_exists(self, datasrc_client, rrset):
+ '''Check whether an rrset with the given name and type exists. Class,
+ TTL, and Rdata (if any) of the given RRset are ignored.
+ RFC2136 Section 2.4.1.
+ Returns True if the prerequisite is satisfied, False otherwise.
+
+ Note: the only thing used in the call to find() here is the
+ result status. The actual data is immediately dropped. As
+ a future optimization, we may want to add a find() option to
+ only return what the result code would be (and not read/copy
+ any actual data).
+ '''
+ _, finder = datasrc_client.find_zone(rrset.get_name())
+ result, _, _ = finder.find(rrset.get_name(), rrset.get_type(),
+ finder.NO_WILDCARD | finder.FIND_GLUE_OK)
+ return result == finder.SUCCESS
+
+ def __prereq_rrset_exists_value(self, datasrc_client, rrset):
+ '''Check whether an rrset that matches name, type, and rdata(s) of the
+ given rrset exists.
+ RFC2136 Section 2.4.2
+ Returns True if the prerequisite is satisfied, False otherwise.
+ '''
+ _, finder = datasrc_client.find_zone(rrset.get_name())
+ result, found_rrset, _ = finder.find(rrset.get_name(), rrset.get_type(),
+ finder.NO_WILDCARD |
+ finder.FIND_GLUE_OK)
+ if result == finder.SUCCESS and\
+ rrset.get_name() == found_rrset.get_name() and\
+ rrset.get_type() == found_rrset.get_type():
+ # We need to match all actual RRs, unfortunately there is no
+ # direct order-independent comparison for rrsets, so this
+ # a slightly inefficient way to handle that.
+
+ # shallow copy of the rdata list, so we are sure that this
+ # loop does not mess with actual data.
+ found_rdata = copy.copy(found_rrset.get_rdata())
+ for rdata in rrset.get_rdata():
+ if rdata in found_rdata:
+ found_rdata.remove(rdata)
+ else:
+ return False
+ return len(found_rdata) == 0
+ return False
+
+ def __prereq_rrset_does_not_exist(self, datasrc_client, rrset):
+ '''Check whether no rrsets with the same name and type as the given
+ rrset exist.
+ RFC2136 Section 2.4.3.
+ Returns True if the prerequisite is satisfied, False otherwise.
+ '''
+ return not self.__prereq_rrset_exists(datasrc_client, rrset)
+
+ def __prereq_name_in_use(self, datasrc_client, rrset):
+ '''Check whether the name of the given RRset is in use (i.e. has
+ 1 or more RRs).
+ RFC2136 Section 2.4.4
+ Returns True if the prerequisite is satisfied, False otherwise.
+
+ Note: the only thing used in the call to find_all() here is
+ the result status. The actual data is immediately dropped. As
+ a future optimization, we may want to add a find_all() option
+ to only return what the result code would be (and not read/copy
+ any actual data).
+ '''
+ _, finder = datasrc_client.find_zone(rrset.get_name())
+ result, rrsets, flags = finder.find_all(rrset.get_name(),
+ finder.NO_WILDCARD |
+ finder.FIND_GLUE_OK)
+ if result == finder.SUCCESS and\
+ (flags & finder.RESULT_WILDCARD == 0):
+ return True
+ return False
+
+ def __prereq_name_not_in_use(self, datasrc_client, rrset):
+ '''Check whether the name of the given RRset is not in use (i.e. does
+ not exist at all, or is an empty nonterminal.
+ RFC2136 Section 2.4.5.
+ Returns True if the prerequisite is satisfied, False otherwise.
+ '''
+ return not self.__prereq_name_in_use(datasrc_client, rrset)
+
+ def __check_prerequisites(self, datasrc_client, zname, zclass):
+ '''Check the prerequisites section of the UPDATE Message.
+ RFC2136 Section 2.4.
+ Returns a dns Rcode signaling either no error (Rcode.NOERROR())
+ or that one of the prerequisites failed (any other Rcode).
+ '''
+ for rrset in self.__message.get_section(SECTION_PREREQUISITE):
+ # First check if the name is in the zone
+ relation = rrset.get_name().compare(zname).get_relation()
+ if relation != NameComparisonResult.SUBDOMAIN and\
+ relation != NameComparisonResult.EQUAL:
+ logger.info(LIBDDNS_PREREQ_NOTZONE,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset))
+ return Rcode.NOTZONE()
+
+ # Algorithm taken from RFC2136 Section 3.2
+ if rrset.get_class() == RRClass.ANY():
+ if rrset.get_ttl().get_value() != 0 or\
+ rrset.get_rdata_count() != 0:
+ logger.info(LIBDDNS_PREREQ_FORMERR_ANY,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset))
+ return Rcode.FORMERR()
+ elif rrset.get_type() == RRType.ANY():
+ if not self.__prereq_name_in_use(datasrc_client,
+ rrset):
+ rcode = Rcode.NXDOMAIN()
+ logger.info(LIBDDNS_PREREQ_NAME_IN_USE_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset), rcode)
+ return rcode
+ else:
+ if not self.__prereq_rrset_exists(datasrc_client, rrset):
+ rcode = Rcode.NXRRSET()
+ logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset), rcode)
+ return rcode
+ elif rrset.get_class() == RRClass.NONE():
+ if rrset.get_ttl().get_value() != 0 or\
+ rrset.get_rdata_count() != 0:
+ logger.info(LIBDDNS_PREREQ_FORMERR_NONE,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset))
+ return Rcode.FORMERR()
+ elif rrset.get_type() == RRType.ANY():
+ if not self.__prereq_name_not_in_use(datasrc_client,
+ rrset):
+ rcode = Rcode.YXDOMAIN()
+ logger.info(LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset), rcode)
+ return rcode
+ else:
+ if not self.__prereq_rrset_does_not_exist(datasrc_client,
+ rrset):
+ rcode = Rcode.YXRRSET()
+ logger.info(LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset), rcode)
+ return rcode
+ elif rrset.get_class() == zclass:
+ if rrset.get_ttl().get_value() != 0:
+ logger.info(LIBDDNS_PREREQ_FORMERR,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset))
+ return Rcode.FORMERR()
+ else:
+ if not self.__prereq_rrset_exists_value(datasrc_client,
+ rrset):
+ rcode = Rcode.NXRRSET()
+ logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset), rcode)
+ return rcode
+ else:
+ logger.info(LIBDDNS_PREREQ_FORMERR_CLASS,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass),
+ RRsetFormatter(rrset))
+ return Rcode.FORMERR()
+
+ # All prerequisites are satisfied
+ return Rcode.NOERROR()
diff --git a/src/lib/python/isc/ddns/tests/Makefile.am b/src/lib/python/isc/ddns/tests/Makefile.am
new file mode 100644
index 0000000..4235a2b
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/Makefile.am
@@ -0,0 +1,28 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = session_tests.py zone_config_tests.py
+EXTRA_DIST = $(PYTESTS)
+CLEANFILES = $(builddir)/rwtest.sqlite3.copied
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# B10_FROM_BUILD is necessary to load data source backend from the build tree.
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ TESTDATA_WRITE_PATH=$(builddir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
new file mode 100644
index 0000000..7f15480
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -0,0 +1,669 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+import os
+import shutil
+import isc.log
+import unittest
+from isc.dns import *
+from isc.datasrc import DataSourceClient
+from isc.ddns.session import *
+from isc.ddns.zone_config import *
+
+# Some common test parameters
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
+WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
+
+TEST_ZONE_NAME = Name('example.org')
+UPDATE_RRTYPE = RRType.SOA()
+TEST_RRCLASS = RRClass.IN()
+TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE)
+TEST_CLIENT6 = ('2001:db8::1', 53, 0, 0)
+TEST_CLIENT4 = ('192.0.2.1', 53)
+# TSIG key for tests when needed. The key name is TEST_ZONE_NAME.
+TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==")
+
+def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[],
+ tsig_key=None):
+ msg = Message(Message.RENDER)
+ msg.set_qid(5353) # arbitrary chosen
+ msg.set_opcode(Opcode.UPDATE())
+ msg.set_rcode(Rcode.NOERROR())
+ for z in zones:
+ msg.add_question(z)
+ for p in prerequisites:
+ msg.add_rrset(SECTION_PREREQUISITE, p)
+
+ renderer = MessageRenderer()
+ if tsig_key is not None:
+ msg.to_wire(renderer, TSIGContext(tsig_key))
+ else:
+ msg.to_wire(renderer)
+
+ # re-read the created data in the parse mode
+ msg.clear(Message.PARSE)
+ msg.from_wire(renderer.get_data())
+
+ return msg
+
+class SesseionTestBase(unittest.TestCase):
+ '''Base class for all sesion related tests.
+
+ It just initializes common test parameters in its setUp() and defines
+ some common utility method(s).
+
+ '''
+ def setUp(self):
+ shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
+ self._datasrc_client = DataSourceClient("sqlite3",
+ WRITE_ZONE_DB_CONFIG)
+ self._update_msg = create_update_msg()
+ self._acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "ACCEPT"}])}
+ self._session = UpdateSession(self._update_msg, TEST_CLIENT4,
+ ZoneConfig([], TEST_RRCLASS,
+ self._datasrc_client,
+ self._acl_map))
+
+ def check_response(self, msg, expected_rcode):
+ '''Perform common checks on update resposne message.'''
+ self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
+ # note: we convert opcode to text it'd be more helpful on failure.
+ self.assertEqual(Opcode.UPDATE().to_text(), msg.get_opcode().to_text())
+ self.assertEqual(expected_rcode.to_text(), msg.get_rcode().to_text())
+ # All sections should be cleared
+ self.assertEqual(0, msg.get_rr_count(SECTION_ZONE))
+ self.assertEqual(0, msg.get_rr_count(SECTION_PREREQUISITE))
+ self.assertEqual(0, msg.get_rr_count(SECTION_UPDATE))
+ self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
+
+class SessionTest(SesseionTestBase):
+ '''Basic session tests'''
+
+ def test_handle(self):
+ '''Basic update case'''
+ result, zname, zclass = self._session.handle()
+ self.assertEqual(UPDATE_SUCCESS, result)
+ self.assertEqual(TEST_ZONE_NAME, zname)
+ self.assertEqual(TEST_RRCLASS, zclass)
+
+ # Just checking these are different from the success code.
+ self.assertNotEqual(UPDATE_ERROR, result)
+ self.assertNotEqual(UPDATE_DROP, result)
+
+ def test_broken_request(self):
+ # Zone section is empty
+ msg = create_update_msg(zones=[])
+ session = UpdateSession(msg, TEST_CLIENT6, None)
+ result, zname, zclass = session.handle()
+ self.assertEqual(UPDATE_ERROR, result)
+ self.assertEqual(None, zname)
+ self.assertEqual(None, zclass)
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ # Zone section contains multiple records
+ msg = create_update_msg(zones=[TEST_ZONE_RECORD, TEST_ZONE_RECORD])
+ session = UpdateSession(msg, TEST_CLIENT4, None)
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ # Zone section's type is not SOA
+ msg = create_update_msg(zones=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.A())])
+ session = UpdateSession(msg, TEST_CLIENT4, None)
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ def test_update_secondary(self):
+ # specified zone is configured as a secondary. Since this
+ # implementation doesn't support update forwarding, the result
+ # should be NOTIMP.
+ msg = create_update_msg(zones=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+ RRType.SOA())])
+ session = UpdateSession(msg, TEST_CLIENT4,
+ ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self._datasrc_client))
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.NOTIMP())
+
+ def check_notauth(self, zname, zclass=TEST_RRCLASS):
+ '''Common test sequence for the 'notauth' test'''
+ msg = create_update_msg(zones=[Question(zname, zclass, RRType.SOA())])
+ session = UpdateSession(msg, TEST_CLIENT4,
+ ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self._datasrc_client))
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.NOTAUTH())
+
+ def test_update_notauth(self):
+ '''Update attempt for non authoritative zones'''
+ # zone name doesn't match
+ self.check_notauth(Name('example.com'))
+ # zone name is a subdomain of the actual authoritative zone
+ # (match must be exact)
+ self.check_notauth(Name('sub.example.org'))
+ # zone class doesn't match
+ self.check_notauth(Name('example.org'), RRClass.CH())
+
+ def __prereq_helper(self, method, expected, rrset):
+ '''Calls the given method with self._datasrc_client
+ and the given rrset, and compares the return value.
+ Function does not do much but makes the code look nicer'''
+ self.assertEqual(expected, method(self._datasrc_client, rrset))
+
+ def __check_prerequisite_exists_combined(self, method, rrclass, expected):
+ '''shared code for the checks for the very similar (but reversed
+ in behaviour) methods __prereq_rrset_exists and
+ __prereq_rrset_does_not_exist.
+ For rrset_exists, rrclass should be ANY, for rrset_does_not_exist,
+ it should be NONE.
+ '''
+ # Basic existence checks
+ # www.example.org should have an A, but not an MX
+ rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+ rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ rrclass, isc.dns.RRType.MX(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ # example.org should have an MX, but not an A
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ rrclass, isc.dns.RRType.MX(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ # Also check the case where the name does not even exist
+ rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ # Wildcard expansion should not be applied, but literal matches
+ # should work
+ rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("*.wildcard.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ # Likewise, CNAME directly should match, but what it points to should
+ # not
+ rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"),
+ rrclass, isc.dns.RRType.CNAME(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ # And also make sure a delegation (itself) is not treated as existing
+ # data
+ rrset = isc.dns.RRset(isc.dns.Name("foo.sub.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+ # But the delegation data itself should match
+ rrset = isc.dns.RRset(isc.dns.Name("sub.example.org"),
+ rrclass, isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+ # As should glue
+ rrset = isc.dns.RRset(isc.dns.Name("ns.sub.example.org"),
+ rrclass, isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ def test_check_prerequisite_exists(self):
+ method = self._session._UpdateSession__prereq_rrset_exists
+ self.__check_prerequisite_exists_combined(method,
+ isc.dns.RRClass.ANY(),
+ True)
+
+ def test_check_prerequisite_does_not_exist(self):
+ method = self._session._UpdateSession__prereq_rrset_does_not_exist
+ self.__check_prerequisite_exists_combined(method,
+ isc.dns.RRClass.NONE(),
+ False)
+
+ def test_check_prerequisite_exists_value(self):
+ method = self._session._UpdateSession__prereq_rrset_exists_value
+
+ rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ # empty one should not match
+ self.__prereq_helper(method, False, rrset)
+
+ # When the rdata is added, it should match
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.1"))
+ self.__prereq_helper(method, True, rrset)
+
+ # But adding more should not
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.2"))
+ self.__prereq_helper(method, False, rrset)
+
+ # Also test one with more than one RR
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns1.example.org."))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns2.example.org."))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns3.example.org."))
+ self.__prereq_helper(method, True, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns4.example.org."))
+ self.__prereq_helper(method, False, rrset)
+
+ # Repeat that, but try a different order of Rdata addition
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns3.example.org."))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns2.example.org."))
+ self.__prereq_helper(method, False, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns1.example.org."))
+ self.__prereq_helper(method, True, rrset)
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns4.example.org."))
+ self.__prereq_helper(method, False, rrset)
+
+ # and test one where the name does not even exist
+ rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.1"))
+ self.__prereq_helper(method, False, rrset)
+
+ def __check_prerequisite_name_in_use_combined(self, method, rrclass,
+ expected):
+ '''shared code for the checks for the very similar (but reversed
+ in behaviour) methods __prereq_name_in_use and
+ __prereq_name_not_in_use
+ '''
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("belowdelegation.sub.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+
+ # empty nonterminal should not match
+ rrset = isc.dns.RRset(isc.dns.Name("nonterminal.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, not expected, rrset)
+ rrset = isc.dns.RRset(isc.dns.Name("empty.nonterminal.example.org"),
+ rrclass, isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+ self.__prereq_helper(method, expected, rrset)
+
+ def test_check_prerequisite_name_in_use(self):
+ method = self._session._UpdateSession__prereq_name_in_use
+ self.__check_prerequisite_name_in_use_combined(method,
+ isc.dns.RRClass.ANY(),
+ True)
+
+ def test_check_prerequisite_name_not_in_use(self):
+ method = self._session._UpdateSession__prereq_name_not_in_use
+ self.__check_prerequisite_name_in_use_combined(method,
+ isc.dns.RRClass.NONE(),
+ False)
+
+ def check_prerequisite_result(self, expected, prerequisites):
+ '''Helper method for checking the result of a prerequisite check;
+ creates an update session, and fills it with the list of rrsets
+ from 'prerequisites'. Then checks if __check_prerequisites()
+ returns the Rcode specified in 'expected'.'''
+ msg = create_update_msg([TEST_ZONE_RECORD], prerequisites)
+ zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ self._acl_map)
+ session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+ # compare the to_text output of the rcodes (nicer error messages)
+ # This call itself should also be done by handle(),
+ # but just for better failures, it is first called on its own
+ self.assertEqual(expected.to_text(),
+ session._UpdateSession__check_prerequisites(self._datasrc_client,
+ TEST_ZONE_NAME,
+ TEST_RRCLASS).to_text())
+ # Now see if handle finds the same result
+ (result, _, _) = session.handle()
+ self.assertEqual(expected,
+ session._UpdateSession__message.get_rcode())
+ # And that the result looks right
+ if expected == Rcode.NOERROR():
+ self.assertEqual(UPDATE_SUCCESS, result)
+ else:
+ self.assertEqual(UPDATE_ERROR, result)
+
+ def test_check_prerequisites(self):
+ # This test checks if the actual prerequisite-type-specific
+ # methods are called.
+ # It does test all types of prerequisites, but it does not test
+ # every possible result for those types (those are tested above,
+ # in the specific prerequisite type tests)
+
+ # Let's first define a number of prereq's that should succeed
+ rrset_exists_yes = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(0))
+
+ rrset_exists_value_yes = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ rrset_exists_value_yes.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.1"))
+
+ rrset_does_not_exist_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(0))
+
+ name_in_use_yes = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+
+ name_not_in_use_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+
+ rrset_exists_value_1 = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ rrset_exists_value_1.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns1.example.org"))
+ rrset_exists_value_2 = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ rrset_exists_value_2.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns2.example.org"))
+ rrset_exists_value_3 = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.NS(),
+ isc.dns.RRTTL(0))
+ rrset_exists_value_3.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(),
+ isc.dns.RRClass.IN(),
+ "ns3.example.org"))
+
+ # and a number that should not
+ rrset_exists_no = isc.dns.RRset(isc.dns.Name("foo.example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(0))
+
+
+ rrset_exists_value_no = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ rrset_exists_value_no.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.2"))
+
+ rrset_does_not_exist_no = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(0))
+
+ name_in_use_no = isc.dns.RRset(isc.dns.Name("foo.example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+
+ name_not_in_use_no = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.ANY(),
+ isc.dns.RRTTL(0))
+
+ # Create an UPDATE with all 5 'yes' prereqs
+ create_update_msg([TEST_ZONE_RECORD],
+ [rrset_exists_yes,
+ rrset_does_not_exist_yes,
+ name_in_use_yes,
+ name_not_in_use_yes,
+ rrset_exists_value_yes,
+ ])
+
+ # check 'no' result codes
+ self.check_prerequisite_result(Rcode.NXRRSET(),
+ [ rrset_exists_no ])
+ self.check_prerequisite_result(Rcode.NXRRSET(),
+ [ rrset_exists_value_no ])
+ self.check_prerequisite_result(Rcode.YXRRSET(),
+ [ rrset_does_not_exist_no ])
+ self.check_prerequisite_result(Rcode.NXDOMAIN(),
+ [ name_in_use_no ])
+ self.check_prerequisite_result(Rcode.YXDOMAIN(),
+ [ name_not_in_use_no ])
+
+ # the 'yes' codes should result in ok
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_exists_yes,
+ rrset_exists_value_yes,
+ rrset_does_not_exist_yes,
+ name_in_use_yes,
+ name_not_in_use_yes,
+ rrset_exists_value_1,
+ rrset_exists_value_2,
+ rrset_exists_value_3])
+
+ # try out a permutation, note that one rrset is split up,
+ # and the order of the RRs should not matter
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_exists_value_3,
+ rrset_exists_yes,
+ rrset_exists_value_2,
+ name_in_use_yes,
+ rrset_exists_value_1])
+
+ # Should fail on the first error, even if most of the
+ # prerequisites are ok
+ self.check_prerequisite_result(Rcode.NXDOMAIN(),
+ [ rrset_exists_value_3,
+ rrset_exists_yes,
+ rrset_exists_value_2,
+ name_in_use_yes,
+ name_in_use_no,
+ rrset_exists_value_1])
+
+ def test_prerequisite_notzone(self):
+ rrset = isc.dns.RRset(isc.dns.Name("some.other.zone."),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(0))
+ self.check_prerequisite_result(Rcode.NOTZONE(), [ rrset ])
+
+ def test_prerequisites_formerr(self):
+ # test for form errors in the prerequisite section
+
+ # Class ANY, non-zero TTL
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(1))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+ # Class ANY, but with rdata
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.ANY(),
+ isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.ANY(),
+ "\# 04 00 00 00 00"))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+ # Class NONE, non-zero TTL
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.SOA(),
+ isc.dns.RRTTL(1))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+ # Class NONE, but with rdata
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.NONE(),
+ isc.dns.RRType.A(),
+ isc.dns.RRTTL(0))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.NONE(),
+ "\# 04 00 00 00 00"))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+ # Matching class and type, but non-zero TTL
+ rrset = isc.dns.RRset(isc.dns.Name("www.example.org"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.A(),
+ isc.dns.RRTTL(1))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(),
+ isc.dns.RRClass.IN(),
+ "192.0.2.1"))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+ # Completely different class
+ rrset = isc.dns.RRset(isc.dns.Name("example.org"),
+ isc.dns.RRClass.CH(),
+ isc.dns.RRType.TXT(),
+ isc.dns.RRTTL(0))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.TXT(),
+ isc.dns.RRClass.CH(),
+ "foo"))
+ self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
+
+class SessionACLTest(SesseionTestBase):
+ '''ACL related tests for update session.'''
+ def test_update_acl_check(self):
+ '''Test for various ACL checks.
+
+ Note that accepted cases are covered in the basic tests.
+
+ '''
+ # create a separate session, with default (empty) ACL map.
+ session = UpdateSession(self._update_msg,
+ TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ self._datasrc_client))
+ # then the request should be rejected.
+ self.assertEqual((UPDATE_ERROR, None, None), session.handle())
+
+ # recreate the request message, and test with an ACL that would result
+ # in 'DROP'. get_message() should return None.
+ msg = create_update_msg()
+ acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "DROP", "from":
+ TEST_CLIENT4[0]}])}
+ session = UpdateSession(msg, TEST_CLIENT4,
+ ZoneConfig([], TEST_RRCLASS,
+ self._datasrc_client, acl_map))
+ self.assertEqual((UPDATE_DROP, None, None), session.handle())
+ self.assertEqual(None, session.get_message())
+
+ def test_update_tsigacl_check(self):
+ '''Test for various ACL checks using TSIG.'''
+ # This ACL will accept requests from TEST_CLIENT4 (any port) *and*
+ # has TSIG signed by TEST_ZONE_NAME; all others will be rejected.
+ acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "ACCEPT",
+ "from": TEST_CLIENT4[0],
+ "key": TEST_ZONE_NAME.to_text()}])}
+
+ # If the message doesn't contain TSIG, it doesn't match the ACCEPT
+ # ACL entry, and the request should be rejected.
+ session = UpdateSession(self._update_msg,
+ TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ self._datasrc_client,
+ acl_map))
+ self.assertEqual((UPDATE_ERROR, None, None), session.handle())
+ self.check_response(session.get_message(), Rcode.REFUSED())
+
+ # If the message contains TSIG, it should match the ACCEPT
+ # ACL entry, and the request should be granted.
+ session = UpdateSession(create_update_msg(tsig_key=TEST_TSIG_KEY),
+ TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS,
+ self._datasrc_client,
+ acl_map))
+ self.assertEqual((UPDATE_SUCCESS, TEST_ZONE_NAME, TEST_RRCLASS),
+ session.handle())
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/ddns/tests/zone_config_tests.py b/src/lib/python/isc/ddns/tests/zone_config_tests.py
new file mode 100644
index 0000000..4efd1c1
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/zone_config_tests.py
@@ -0,0 +1,166 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+import isc.log
+from isc.dns import *
+from isc.datasrc import DataSourceClient
+from isc.ddns.zone_config import *
+import isc.acl.dns
+from isc.acl.acl import ACCEPT, REJECT, DROP, LoaderError
+
+import unittest
+import socket
+
+# Some common test parameters
+TEST_ZONE_NAME = Name('example.org')
+TEST_SECONDARY_ZONE_NAME = Name('example.com')
+TEST_RRCLASS = RRClass.IN()
+TEST_TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
+TEST_ACL_CONTEXT = isc.acl.dns.RequestContext(
+ socket.getaddrinfo("192.0.2.1", 1234, 0, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP, socket.AI_NUMERICHOST)[0][4])
+
+class FakeDataSourceClient:
+ '''Faked data source client used in the ZoneConfigTest.
+
+ It emulates isc.datasrc.DataSourceClient, but only has to provide
+ the find_zone() interface (and only the first element of the return
+ value matters). By default it returns 'SUCCESS' (exact match) for
+ any input. It can be dynamically customized via the set_find_result()
+ method.
+
+ '''
+ def __init__(self):
+ self.__find_result = DataSourceClient.SUCCESS
+
+ def find_zone(self, zname):
+ return (self.__find_result, None)
+
+ def set_find_result(self, result):
+ self.__find_result = result
+
+class ZoneConfigTest(unittest.TestCase):
+ '''Some basic tests for the ZoneConfig class.'''
+ def setUp(self):
+ self.__datasrc_client = FakeDataSourceClient()
+ self.zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self.__datasrc_client)
+
+ def test_find_zone(self):
+ # Primay zone case: zone is in the data source, and not in secondaries
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ (self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+
+ # Secondary zone case: zone is in the data source and in secondaries.
+ self.assertEqual((ZONE_SECONDARY, None),
+ (self.zconfig.find_zone(TEST_SECONDARY_ZONE_NAME,
+ TEST_RRCLASS)))
+
+ # 'not found' case: zone not in the data source.
+ self.__datasrc_client.set_find_result(DataSourceClient.NOTFOUND)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(Name('example'),
+ TEST_RRCLASS)))
+ # same for the partial match
+ self.__datasrc_client.set_find_result(DataSourceClient.PARTIALMATCH)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(Name('example'),
+ TEST_RRCLASS)))
+ # a bit unusual case: zone not in the data source, but in secondaries.
+ # this is probably a configuration error, but ZoneConfig doesn't do
+ # this level check.
+ self.__datasrc_client.set_find_result(DataSourceClient.NOTFOUND)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(TEST_ZONE_NAME,
+ TEST_RRCLASS)))
+ # zone class doesn't match (but zone name matches)
+ self.__datasrc_client.set_find_result(DataSourceClient.SUCCESS)
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ RRClass.CH(), self.__datasrc_client)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+ # similar to the previous case, but also in the secondary list
+ zconfig = ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ RRClass.CH(), self.__datasrc_client)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+
+ # check some basic tests varying the secondary list.
+ # empty secondary list doesn't cause any disruption.
+ zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
+ # adding some mulitle tuples, including subdomainof the test zone name,
+ # and the same zone name but a different class
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
+ (Name('example'), TEST_RRCLASS),
+ (Name('sub.example.org'), TEST_RRCLASS),
+ (TEST_ZONE_NAME, RRClass.CH())],
+ TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
+ # secondary zone list has a duplicate entry, which is just
+ # (effecitivey) ignored
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
+ (TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS))
+
+class ACLConfigTest(unittest.TestCase):
+ def setUp(self):
+ self.__datasrc_client = FakeDataSourceClient()
+ self.__zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self.__datasrc_client)
+
+ def test_get_update_acl(self):
+ # By default, no ACL is set, and the default ACL is "reject all"
+ acl = self.__zconfig.get_update_acl(TEST_ZONE_NAME, TEST_RRCLASS)
+ self.assertEqual(REJECT, acl.execute(TEST_ACL_CONTEXT))
+
+ # Add a map entry that would match the request, and it should now be
+ # accepted.
+ acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "ACCEPT"}])}
+ self.__zconfig.set_update_acl_map(acl_map)
+ acl = self.__zconfig.get_update_acl(TEST_ZONE_NAME, TEST_RRCLASS)
+ self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
+
+ # 'All reject' ACL will still apply for any other zones
+ acl = self.__zconfig.get_update_acl(Name('example.com'), TEST_RRCLASS)
+ self.assertEqual(REJECT, acl.execute(TEST_ACL_CONTEXT))
+ acl = self.__zconfig.get_update_acl(TEST_ZONE_NAME, RRClass.CH())
+ self.assertEqual(REJECT, acl.execute(TEST_ACL_CONTEXT))
+
+ # Test with a map with a few more ACL entries. Should be nothing
+ # special.
+ acl_map = {(Name('example.com'), TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "REJECT"}]),
+ (TEST_ZONE_NAME, TEST_RRCLASS):
+ REQUEST_LOADER.load([{"action": "ACCEPT"}]),
+ (TEST_ZONE_NAME, RRClass.CH()):
+ REQUEST_LOADER.load([{"action": "DROP"}])}
+ self.__zconfig.set_update_acl_map(acl_map)
+ acl = self.__zconfig.get_update_acl(TEST_ZONE_NAME, TEST_RRCLASS)
+ self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
+ acl = self.__zconfig.get_update_acl(Name('example.com'), TEST_RRCLASS)
+ self.assertEqual(REJECT, acl.execute(TEST_ACL_CONTEXT))
+ acl = self.__zconfig.get_update_acl(TEST_ZONE_NAME, RRClass.CH())
+ self.assertEqual(DROP, acl.execute(TEST_ACL_CONTEXT))
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/ddns/zone_config.py b/src/lib/python/isc/ddns/zone_config.py
new file mode 100644
index 0000000..388770c
--- /dev/null
+++ b/src/lib/python/isc/ddns/zone_config.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+from isc.acl.dns import REQUEST_LOADER
+import isc.dns
+from isc.datasrc import DataSourceClient
+
+# Constants representing zone types
+ZONE_NOTFOUND = -1 # Zone isn't found in find_zone()
+ZONE_PRIMARY = 0 # Primary zone
+ZONE_SECONDARY = 1 # Secondary zone
+
+class ZoneConfig:
+ '''A temporary helper class to encapsulate zone related configuration.
+
+ Its find_zone method will search the conceptual configuration for a
+ given zone, and return a tuple of zone type (primary or secondary) and
+ the client object to access the data source stroing the zone.
+ It's very likely that details of zone related configurations like this
+ will change in near future, so the main purpose of this class is to
+ provide an independent interface for the main DDNS session module
+ until the details are fixed.
+
+ '''
+ def __init__(self, secondaries, datasrc_class, datasrc_client, acl_map={}):
+ '''Constructor.
+
+ Parameters:
+ - secondaries: a list of 2-element tuples. Each element is a pair
+ of isc.dns.Name and isc.dns.RRClass, and identifies a single
+ secondary zone.
+ - datasrc_class: isc.dns.RRClass object. Specifies the RR class
+ of datasrc_client.
+ - datasrc_client: isc.dns.DataSourceClient object. A data source
+ class for the RR class of datasrc_class. It's expected to contain
+ a zone that is eventually updated in the ddns package.
+ - acl_map: a dictionary that maps a tuple of
+ (isc.dns.Name, isc.dns.RRClass) to an isc.dns.dns.RequestACL
+ object. It defines an ACL to be applied to the zone defined
+ by the tuple. If unspecified, or the map is empty, the default
+ ACL will be applied to all zones, which is to reject any requests.
+
+ '''
+ self.__secondaries = set()
+ for (zname, zclass) in secondaries:
+ self.__secondaries.add((zname, zclass))
+ self.__datasrc_class = datasrc_class
+ self.__datasrc_client = datasrc_client
+ self.__default_acl = REQUEST_LOADER.load([{"action": "REJECT"}])
+ self.__acl_map = acl_map
+
+ def find_zone(self, zone_name, zone_class):
+ '''Return the type and accessor client object for given zone.'''
+ if self.__datasrc_class == zone_class and \
+ self.__datasrc_client.find_zone(zone_name)[0] == \
+ DataSourceClient.SUCCESS:
+ if (zone_name, zone_class) in self.__secondaries:
+ return ZONE_SECONDARY, None
+ return ZONE_PRIMARY, self.__datasrc_client
+ return ZONE_NOTFOUND, None
+
+ def get_update_acl(self, zone_name, zone_class):
+ '''Return the update ACL for the given zone.
+
+ This method searches the internally stored ACL map to see if
+ there's an ACL to be applied to the given zone. If found, that
+ ACL will be returned; otherwise the default ACL (see the constructor
+ description) will be returned.
+
+ Parameters:
+ zone_name (isc.dns.Name): The zone name.
+ zone_class (isc.dns.RRClass): The zone class.
+ '''
+ acl = self.__acl_map.get((zone_name, zone_class))
+ if acl is not None:
+ return acl
+ return self.__default_acl
+
+ def set_update_acl_map(self, new_map):
+ '''Set a new ACL map.
+
+ This replaces any stored ACL map, either at construction or
+ by a previous call to this method, with the given new one.
+
+ Parameter:
+ new_map: same as the acl_map parameter of the constructor.
+
+ '''
+ self.__acl_map = new_map
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index ed05398..69e70b7 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -541,8 +541,14 @@ Logger_performOutput(Function function, PyObject* args, bool dbgLevel) {
// into the formatter. It will print itself in the end.
for (size_t i(start); i < number; ++ i) {
PyObjectContainer param_container(PySequence_GetItem(args, i));
- formatter = formatter.arg(objectToStr(param_container.get(),
- true));
+ try {
+ formatter = formatter.arg(objectToStr(param_container.get(),
+ true));
+ }
+ catch (...) {
+ formatter.deactivate();
+ throw;
+ }
}
Py_RETURN_NONE;
}
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 6b4be94..6d23df3 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -12,6 +12,7 @@ EXTRA_DIST += zonemgr_messages.py
EXTRA_DIST += cfgmgr_messages.py
EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
+EXTRA_DIST += libddns_messages.py
EXTRA_DIST += libxfrin_messages.py
EXTRA_DIST += server_common_messages.py
EXTRA_DIST += dbutil_messages.py
@@ -28,6 +29,7 @@ CLEANFILES += zonemgr_messages.pyc
CLEANFILES += cfgmgr_messages.pyc
CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
+CLEANFILES += libddns_messages.pyc
CLEANFILES += libxfrin_messages.pyc
CLEANFILES += server_common_messages.pyc
CLEANFILES += dbutil_messages.pyc
diff --git a/src/lib/python/isc/log_messages/libddns_messages.py b/src/lib/python/isc/log_messages/libddns_messages.py
new file mode 100644
index 0000000..58d886d
--- /dev/null
+++ b/src/lib/python/isc/log_messages/libddns_messages.py
@@ -0,0 +1 @@
+from work.libddns_messages import *
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
index 80fa909..2d3f937 100644
--- a/src/lib/python/isc/xfrin/diff.py
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -15,11 +15,12 @@
"""
This helps the XFR in process with accumulating parts of diff and applying
-it to the datasource.
+it to the datasource. It also has a 'single update mode' which is useful
+for DDNS.
The name of the module is not yet fully decided. We might want to move it
-under isc.datasrc or somewhere else, because we might want to reuse it with
-future DDNS process. But until then, it lives here.
+under isc.datasrc or somewhere else, because we are reusing it with DDNS.
+But for now, it lives here.
"""
import isc.dns
@@ -59,7 +60,8 @@ class Diff:
the changes to underlying data source right away, but keeps them for
a while.
"""
- def __init__(self, ds_client, zone, replace=False, journaling=False):
+ def __init__(self, ds_client, zone, replace=False, journaling=False,
+ single_update_mode=False):
"""
Initializes the diff to a ready state. It checks the zone exists
in the datasource and if not, NoSuchZone is raised. This also creates
@@ -76,6 +78,25 @@ class Diff:
incoming updates but does not support journaling, the Diff object
will still continue applying the diffs with disabling journaling.
+ If single_update_mode is true, the update is expected to only contain
+ 1 set of changes (i.e. one set of additions, and one set of deletions).
+ If so, the additions and deletions are kept separately, and applied
+ in one go upon commit() or apply(). In this mode, additions and
+ deletions can be done in any order. The first addition and the
+ first deletion still have to be the new and old SOA records,
+ respectively. Once apply() or commit() has been called, this
+ requirement is renewed (since the diff object is essentialy reset).
+
+ In this single_update_mode, upon commit, the deletions are performed
+ first, and then the additions. With the previously mentioned
+ restrictions, this means that the actual update looks like a single
+ IXFR changeset (which can then be journaled). Apart from those
+ restrictions, this class does not do any checking of data; it is
+ the caller's responsibility to keep the data 'sane', and this class
+ does not presume to have any knowledge of DNS zone content sanity.
+ For instance, though it enforces the SOA to be deleted first, and
+ added first, it does no checks on the SERIAL value.
+
You can also expect isc.datasrc.Error or isc.datasrc.NotImplemented
exceptions.
"""
@@ -91,7 +112,12 @@ class Diff:
raise NoSuchZone("Zone " + str(zone) +
" does not exist in the data source " +
str(ds_client))
- self.__buffer = []
+ self.__single_update_mode = single_update_mode
+ if single_update_mode:
+ self.__additions = []
+ self.__deletions = []
+ else:
+ self.__buffer = []
def __check_commited(self):
"""
@@ -103,12 +129,45 @@ class Diff:
raise ValueError("The diff is already commited or it has raised " +
"an exception, you come late")
+ def __append_with_soa_check(self, buf, operation, rr):
+ """
+ Helper method for __data_common().
+ Add the given rr to the given buffer, but with a SOA check;
+ - if the buffer is empty, the RRType of the rr must be SOA
+ - if the buffer is not empty, the RRType must not be SOA
+ Raises a ValueError if these rules are not satisified.
+ If they are, the RR is appended to the buffer.
+ Arguments:
+ buf: buffer to add to
+ operation: operation to perform (either 'add' or 'delete')
+ rr: RRset to add to the buffer
+ """
+ # first add or delete must be of type SOA
+ if len(buf) == 0 and\
+ rr.get_type() != isc.dns.RRType.SOA():
+ raise ValueError("First " + operation +
+ " in single update mode must be of type SOA")
+ # And later adds or deletes may not
+ elif len(buf) != 0 and\
+ rr.get_type() == isc.dns.RRType.SOA():
+ raise ValueError("Multiple SOA records in single " +
+ "update mode " + operation)
+ buf.append((operation, rr))
+
def __data_common(self, rr, operation):
"""
Schedules an operation with rr.
It does all the real work of add_data and delete_data, including
all checks.
+
+ Raises a ValueError in several cases:
+ - if the rrset contains multiple rrs
+ - if the class of the rrset does not match that of the update
+ - in single_update_mode if the first rr is not of type SOA (both
+ for addition and deletion)
+ - in single_update_mode if any later rr is of type SOA (both for
+ addition and deletion)
"""
self.__check_commited()
if rr.get_rdata_count() != 1:
@@ -118,10 +177,17 @@ class Diff:
raise ValueError("The rrset's class " + str(rr.get_class()) +
" does not match updater's " +
str(self.__updater.get_class()))
- self.__buffer.append((operation, rr))
- if len(self.__buffer) >= DIFF_APPLY_TRESHOLD:
- # Time to auto-apply, so the data don't accumulate too much
- self.apply()
+ if self.__single_update_mode:
+ if operation == 'add':
+ self.__append_with_soa_check(self.__additions, operation, rr)
+ elif operation == 'delete':
+ self.__append_with_soa_check(self.__deletions, operation, rr)
+ else:
+ self.__buffer.append((operation, rr))
+ if len(self.__buffer) >= DIFF_APPLY_TRESHOLD:
+ # Time to auto-apply, so the data don't accumulate too much
+ # This is not done for DDNS type data
+ self.apply()
def add_data(self, rr):
"""
@@ -175,23 +241,34 @@ class Diff:
sigdata2 = rrset2.get_rdata()[0].to_text().split()[0]
return sigdata1 == sigdata2
- buf = []
- for (op, rrset) in self.__buffer:
- old = buf[-1][1] if len(buf) > 0 else None
- if old is None or op != buf[-1][0] or \
- rrset.get_name() != old.get_name() or \
- (not same_type(rrset, old)):
- buf.append((op, isc.dns.RRset(rrset.get_name(),
- rrset.get_class(),
- rrset.get_type(),
- rrset.get_ttl())))
- if rrset.get_ttl() != buf[-1][1].get_ttl():
- logger.warn(LIBXFRIN_DIFFERENT_TTL, rrset.get_ttl(),
- buf[-1][1].get_ttl(), rrset.get_name(),
- rrset.get_class(), rrset.get_type())
- for rdatum in rrset.get_rdata():
- buf[-1][1].add_rdata(rdatum)
- self.__buffer = buf
+ def compact_buffer(buffer_to_compact):
+ '''Internal helper function for compacting buffers, compacts the
+ given buffer.
+ Returns the compacted buffer.
+ '''
+ buf = []
+ for (op, rrset) in buffer_to_compact:
+ old = buf[-1][1] if len(buf) > 0 else None
+ if old is None or op != buf[-1][0] or \
+ rrset.get_name() != old.get_name() or \
+ (not same_type(rrset, old)):
+ buf.append((op, isc.dns.RRset(rrset.get_name(),
+ rrset.get_class(),
+ rrset.get_type(),
+ rrset.get_ttl())))
+ if rrset.get_ttl() != buf[-1][1].get_ttl():
+ logger.warn(LIBXFRIN_DIFFERENT_TTL, rrset.get_ttl(),
+ buf[-1][1].get_ttl(), rrset.get_name(),
+ rrset.get_class(), rrset.get_type())
+ for rdatum in rrset.get_rdata():
+ buf[-1][1].add_rdata(rdatum)
+ return buf
+
+ if self.__single_update_mode:
+ self.__additions = compact_buffer(self.__additions)
+ self.__deletions = compact_buffer(self.__deletions)
+ else:
+ self.__buffer = compact_buffer(self.__buffer)
def apply(self):
"""
@@ -209,25 +286,41 @@ class Diff:
It also can raise isc.datasrc.Error. If that happens, you should stop
using this object and abort the modification.
"""
- self.__check_commited()
- # First, compact the data
- self.compact()
- try:
- # Then pass the data inside the data source
- for (operation, rrset) in self.__buffer:
+ def apply_buffer(buf):
+ '''
+ Helper method to apply all operations in the given buffer
+ '''
+ for (operation, rrset) in buf:
if operation == 'add':
self.__updater.add_rrset(rrset)
elif operation == 'delete':
self.__updater.delete_rrset(rrset)
else:
raise ValueError('Unknown operation ' + operation)
+
+ self.__check_commited()
+ # First, compact the data
+ self.compact()
+ try:
+ # Then pass the data inside the data source
+ if self.__single_update_mode:
+ apply_buffer(self.__deletions)
+ apply_buffer(self.__additions)
+ else:
+ apply_buffer(self.__buffer)
+
# As everything is already in, drop the buffer
except:
# If there's a problem, we can't continue.
self.__updater = None
raise
- self.__buffer = []
+ # all went well, reset state of buffers
+ if self.__single_update_mode:
+ self.__additions = []
+ self.__deletions = []
+ else:
+ self.__buffer = []
def commit(self):
"""
@@ -259,5 +352,27 @@ class Diff:
Probably useful only for testing and introspection purposes. Don't
modify the list.
+
+ Raises a ValueError if the buffer is in single_update_mode.
+ """
+ if self.__single_update_mode:
+ raise ValueError("Compound buffer requested in single-update mode")
+ else:
+ return self.__buffer
+
+ def get_single_update_buffers(self):
+ """
+ Returns the current buffers of changes not yet passed into the data
+ source. It is a tuple of the current deletions and additions, which
+ each are in a form like [('delete', rrset), ('delete', rrset), ...],
+ and [('add', rrset), ('add', rrset), ..].
+
+ Probably useful only for testing and introspection purposes. Don't
+ modify the lists.
+
+ Raises a ValueError if the buffer is not in single_update_mode.
"""
- return self.__buffer
+ if not self.__single_update_mode:
+ raise ValueError("Separate buffers requested in single-update mode")
+ else:
+ return (self.__deletions, self.__additions)
diff --git a/src/lib/python/isc/xfrin/tests/diff_tests.py b/src/lib/python/isc/xfrin/tests/diff_tests.py
index 7c1158a..6c545d2 100644
--- a/src/lib/python/isc/xfrin/tests/diff_tests.py
+++ b/src/lib/python/isc/xfrin/tests/diff_tests.py
@@ -478,14 +478,116 @@ class DiffTest(unittest.TestCase):
diff.compact()
self.assertEqual(2, len(diff.get_buffer()))
- def test_relpace(self):
- """
+ def test_replace(self):
+ '''
Test that when we want to replace the whole zone, it is propagated.
- """
+ '''
self.__should_replace = True
diff = Diff(self, "example.org.", True)
self.assertTrue(self.__updater_requested)
+ def test_get_buffer(self):
+ '''
+ Test that the getters raise when used in the wrong mode
+ '''
+ diff_multi = Diff(self, Name('example.org.'), single_update_mode=False)
+ self.assertRaises(ValueError, diff_multi.get_single_update_buffers)
+ self.assertEqual([], diff_multi.get_buffer())
+
+ diff_single = Diff(self, Name('example.org.'), single_update_mode=True)
+ self.assertRaises(ValueError, diff_single.get_buffer)
+ self.assertEqual(([], []), diff_single.get_single_update_buffers())
+
+ def test_single_update_mode(self):
+ '''
+ Test single-update mode. In this mode, updates and deletes can
+ be done in any order, but there may only be one changeset.
+ For both updates and deletes, exactly one SOA rr must be given,
+ and it must be the first change.
+ '''
+
+ # First create some RRsets to play with
+ soa = RRset(Name('example.org.'), self.__rrclass, RRType.SOA(),
+ RRTTL(3600))
+ soa.add_rdata(Rdata(soa.get_type(), soa.get_class(),
+ "ns.example.org. foo.example.org. 1234 28800 "+
+ "7200 604800 3600"))
+
+ a = RRset(Name('www.example.org.'), self.__rrclass, RRType.A(),
+ RRTTL(3600))
+ a.add_rdata(Rdata(a.get_type(), a.get_class(),
+ "192.0.2.1"))
+
+ a2 = RRset(Name('www.example.org.'), self.__rrclass, RRType.A(),
+ RRTTL(3600))
+ a2.add_rdata(Rdata(a2.get_type(), a2.get_class(),
+ "192.0.2.2"))
+
+ # full rrset for A (to check compact())
+ a_1_2 = RRset(Name('www.example.org.'), self.__rrclass, RRType.A(),
+ RRTTL(3600))
+ a_1_2.add_rdata(Rdata(a_1_2.get_type(), a_1_2.get_class(),
+ "192.0.2.1"))
+ a_1_2.add_rdata(Rdata(a_1_2.get_type(), a_1_2.get_class(),
+ "192.0.2.2"))
+
+ diff = Diff(self, Name('example.org.'), single_update_mode=True)
+
+ # adding a first should fail
+ self.assertRaises(ValueError, diff.add_data, a)
+ # But soa should work
+ diff.add_data(soa)
+ # And then A should as well
+ diff.add_data(a)
+ diff.add_data(a2)
+ # But another SOA should fail again
+ self.assertRaises(ValueError, diff.add_data, soa)
+
+ # Same for delete
+ self.assertRaises(ValueError, diff.delete_data, a)
+ diff.delete_data(soa)
+ diff.delete_data(a)
+ diff.delete_data(a2)
+ self.assertRaises(ValueError, diff.delete_data, soa)
+
+ # Not compacted yet, so the buffers should be as we
+ # filled them
+ (delbuf, addbuf) = diff.get_single_update_buffers()
+ self.assertEqual([('delete', soa), ('delete', a), ('delete', a2)], delbuf)
+ self.assertEqual([('add', soa), ('add', a), ('add', a2)], addbuf)
+
+ # Compact should compact the A records in both buffers
+ diff.compact()
+ (delbuf, addbuf) = diff.get_single_update_buffers()
+ # need rrset equality again :/
+ self.assertEqual(2, len(delbuf))
+ self.assertEqual(2, len(delbuf[0]))
+ self.assertEqual('delete', delbuf[0][0])
+ self.assertEqual(soa.to_text(), delbuf[0][1].to_text())
+ self.assertEqual(2, len(delbuf[1]))
+ self.assertEqual('delete', delbuf[1][0])
+ self.assertEqual(a_1_2.to_text(), delbuf[1][1].to_text())
+
+ self.assertEqual(2, len(addbuf))
+ self.assertEqual(2, len(addbuf[0]))
+ self.assertEqual('add', addbuf[0][0])
+ self.assertEqual(soa.to_text(), addbuf[0][1].to_text())
+ self.assertEqual(2, len(addbuf[1]))
+ self.assertEqual('add', addbuf[1][0])
+ self.assertEqual(a_1_2.to_text(), addbuf[1][1].to_text())
+
+ # Apply should reset the buffers
+ diff.apply()
+ (delbuf, addbuf) = diff.get_single_update_buffers()
+ self.assertEqual([], delbuf)
+ self.assertEqual([], addbuf)
+
+ # Now the change has been applied, and the buffers are cleared,
+ # Adding non-SOA records should fail again.
+ self.assertRaises(ValueError, diff.add_data, a)
+ self.assertRaises(ValueError, diff.delete_data, a)
+
+
if __name__ == "__main__":
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
index 829b6d9..9adf84d 100644
--- a/src/lib/server_common/tests/socket_requestor_test.cc
+++ b/src/lib/server_common/tests/socket_requestor_test.cc
@@ -144,6 +144,7 @@ createExpectedRequest(const std::string& address,
packet->add(Element::create("Boss"));
packet->add(Element::create("*"));
packet->add(createCommand("get_socket", command_args));
+ packet->add(Element::create(-1));
return (packet);
}
@@ -284,6 +285,7 @@ createExpectedRelease(const std::string& token) {
packet->add(Element::create("Boss"));
packet->add(Element::create("*"));
packet->add(createCommand("drop_socket", command_args));
+ packet->add(Element::create(-1));
return (packet);
}
diff --git a/src/lib/statistics/counter.cc b/src/lib/statistics/counter.cc
index 9cb1a6f..53dc58e 100644
--- a/src/lib/statistics/counter.cc
+++ b/src/lib/statistics/counter.cc
@@ -1,3 +1,17 @@
+// Copyright (C) 2011 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 <vector>
#include <boost/noncopyable.hpp>
diff --git a/src/lib/statistics/counter.h b/src/lib/statistics/counter.h
index b077616..9e467ce 100644
--- a/src/lib/statistics/counter.h
+++ b/src/lib/statistics/counter.h
@@ -1,3 +1,17 @@
+// Copyright (C) 2011 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 __COUNTER_H
#define __COUNTER_H 1
diff --git a/src/lib/statistics/counter_dict.cc b/src/lib/statistics/counter_dict.cc
index da6aace..55353b2 100644
--- a/src/lib/statistics/counter_dict.cc
+++ b/src/lib/statistics/counter_dict.cc
@@ -1,3 +1,17 @@
+// Copyright (C) 2011 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 <cassert>
#include <stdexcept>
#include <iterator>
diff --git a/src/lib/statistics/counter_dict.h b/src/lib/statistics/counter_dict.h
index 4a4cab1..e322119 100644
--- a/src/lib/statistics/counter_dict.h
+++ b/src/lib/statistics/counter_dict.h
@@ -1,3 +1,17 @@
+// Copyright (C) 2011 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 __COUNTER_DICT_H
#define __COUNTER_DICT_H 1
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index 03aec01..d686da6 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -34,6 +34,7 @@ using namespace isc::asiolink;
namespace isc {
namespace testutils {
const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
+const uint16_t DEFAULT_REMOTE_PORT = 53210;
SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
parse_message(new Message(Message::PARSE)),
@@ -62,7 +63,8 @@ SrvTestBase::createDataFromFile(const char* const datafile,
delete endpoint;
endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+ IOAddress(DEFAULT_REMOTE_ADDRESS),
+ DEFAULT_REMOTE_PORT);
UnitTestUtil::readWireData(datafile, data);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
@@ -71,7 +73,9 @@ SrvTestBase::createDataFromFile(const char* const datafile,
void
SrvTestBase::createRequestPacket(Message& message,
- const int protocol, TSIGContext* context)
+ const int protocol, TSIGContext* context,
+ const char* const remote_address,
+ uint16_t remote_port)
{
if (context == NULL) {
message.toWire(request_renderer);
@@ -81,8 +85,8 @@ SrvTestBase::createRequestPacket(Message& message,
delete io_message;
- endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
+ endpoint = IOEndpoint::create(protocol, IOAddress(remote_address),
+ remote_port);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
@@ -96,9 +100,10 @@ void
SrvTestBase::unsupportedRequest() {
for (unsigned int i = 0; i < 16; ++i) {
// set Opcode to 'i', which iterators over all possible codes except
- // the standard query and notify
+ // the standard opcodes we support.
if (i == isc::dns::Opcode::QUERY().getCode() ||
- i == isc::dns::Opcode::NOTIFY().getCode()) {
+ i == isc::dns::Opcode::NOTIFY().getCode() ||
+ i == isc::dns::Opcode::UPDATE().getCode()) {
continue;
}
createDataFromFile("simplequery_fromWire.wire");
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index b5f64b5..a5c516e 100644
--- a/src/lib/testutils/srv_test.h
+++ b/src/lib/testutils/srv_test.h
@@ -35,6 +35,7 @@ class IOEndpoint;
namespace isc {
namespace testutils {
extern const char* const DEFAULT_REMOTE_ADDRESS;
+extern const uint16_t DEFAULT_REMOTE_PORT;
// These are flags to indicate whether the corresponding flag bit of the
// DNS header is to be set in the test cases. (The flag values
@@ -88,7 +89,9 @@ protected:
/// The existing content of \c io_message, if any, will be deleted.
void createRequestPacket(isc::dns::Message& message,
const int protocol = IPPROTO_UDP,
- isc::dns::TSIGContext* context = NULL);
+ isc::dns::TSIGContext* context = NULL,
+ const char* const address = DEFAULT_REMOTE_ADDRESS,
+ uint16_t port = DEFAULT_REMOTE_PORT);
MockSession notify_session;
MockServer dnsserv;
diff --git a/src/lib/testutils/testdata/rwtest.sqlite3 b/src/lib/testutils/testdata/rwtest.sqlite3
index 5eeb2c3..24afc2c 100644
Binary files a/src/lib/testutils/testdata/rwtest.sqlite3 and b/src/lib/testutils/testdata/rwtest.sqlite3 differ
diff --git a/src/lib/util/io/sockaddr_util.h b/src/lib/util/io/sockaddr_util.h
index 9b4a0cb..0cd7c7b 100644
--- a/src/lib/util/io/sockaddr_util.h
+++ b/src/lib/util/io/sockaddr_util.h
@@ -15,6 +15,7 @@
#ifndef __SOCKADDR_UTIL_H_
#define __SOCKADDR_UTIL_H_ 1
+#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h
index 77f18a3..48b7f19 100644
--- a/src/lib/util/io/socketsession.h
+++ b/src/lib/util/io/socketsession.h
@@ -15,12 +15,14 @@
#ifndef __SOCKETSESSION_H_
#define __SOCKETSESSION_H_ 1
-#include <string>
-
#include <boost/noncopyable.hpp>
#include <exceptions/exceptions.h>
+#include <string>
+
+#include <sys/socket.h>
+
namespace isc {
namespace util {
namespace io {
@@ -156,6 +158,35 @@ public:
isc::Exception(file, line, what) {}
};
+/// The "base" class of \c SocketSessionForwarder
+///
+/// This class defines abstract interfaces of the \c SocketSessionForwarder
+/// class. Although \c SocketSessionForwarder is not intended to be used in
+/// a polymorphic way, it's not easy to use in tests because it will require
+/// various low level network operations. So it would be useful if we
+/// provide a framework for defining a fake or mock version of it.
+/// An application that needs to use \c SocketSessionForwarder would actually
+/// refer to this base class, and tests for the application would define
+/// and use a fake version of the forwarder class.
+///
+/// Normal applications are not expected to define and use their own derived
+/// version of this base class, while it's not prohibited at the API level.
+///
+/// See description of \c SocketSessionForwarder for the expected interface.
+class BaseSocketSessionForwarder {
+protected:
+ BaseSocketSessionForwarder() {}
+
+public:
+ virtual ~BaseSocketSessionForwarder() {}
+ virtual void connectToReceiver() = 0;
+ virtual void close() = 0;
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len) = 0;
+};
+
/// The forwarder of socket sessions
///
/// An object of this class maintains a UNIX domain socket (normally expected
@@ -164,7 +195,9 @@ public:
///
/// See the description of \ref SocketSessionUtility for other details of how
/// the session forwarding works.
-class SocketSessionForwarder : boost::noncopyable {
+class SocketSessionForwarder : boost::noncopyable,
+ public BaseSocketSessionForwarder
+{
public:
/// The constructor.
///
@@ -212,7 +245,7 @@ public:
///
/// If a connection has been established, it's automatically closed in
/// the destructor.
- ~SocketSessionForwarder();
+ virtual ~SocketSessionForwarder();
/// Establish a connection to the receiver.
///
@@ -224,7 +257,7 @@ public:
/// \exception BadValue The method is called while an already
/// established connection is still active.
/// \exception SocketSessionError A system error in socket operation.
- void connectToReceiver();
+ virtual void connectToReceiver();
/// Close the connection to the receiver.
///
@@ -232,7 +265,7 @@ public:
/// As long as it's met this method is exception free.
///
/// \exception BadValue The connection hasn't been established.
- void close();
+ virtual void close();
/// Forward a socket session to the receiver.
///
@@ -276,10 +309,10 @@ public:
/// \param data A pointer to the beginning of the memory region for the
/// session data
/// \param data_len The size of the session data in bytes.
- void push(int sock, int family, int type, int protocol,
- const struct sockaddr& local_end,
- const struct sockaddr& remote_end,
- const void* data, size_t data_len);
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len);
private:
struct ForwarderImpl;
diff --git a/src/lib/util/locks.h b/src/lib/util/locks.h
index daaf216..da9e9cd 100644
--- a/src/lib/util/locks.h
+++ b/src/lib/util/locks.h
@@ -16,7 +16,7 @@
/// It also contains code to use boost/threads locks:
///
///
-/// All locks are dummy classes that don't actually do anything. At this moment,
+/// All locks are dummy classes that don't actually do anything. At this moment,
/// only the very minimal set of methods that we actually use is defined.
///
/// Note that we need to include <config.h> in our .cc files for that
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index 4aea951..37dfc5e 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -39,12 +39,12 @@ run_unittests_SOURCES += range_utilities_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
-run_unittests_LDADD = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD = $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
run_unittests_LDADD += \
$(top_builddir)/src/lib/util/unittests/libutil_unittests.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index 2827471..3007a83 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -11,6 +11,9 @@ libutil_unittests_la_SOURCES += run_all.h run_all.cc
libutil_unittests_la_SOURCES += textdata.h
endif
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+
libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS)
if HAVE_GTEST
libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)
diff --git a/src/lib/util/unittests/mock_socketsession.h b/src/lib/util/unittests/mock_socketsession.h
new file mode 100644
index 0000000..8078265
--- /dev/null
+++ b/src/lib/util/unittests/mock_socketsession.h
@@ -0,0 +1,154 @@
+// 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 __UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+#define __UTIL_UNITTESTS_MOCKSOCKETSESSION_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+#include <cassert>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Mock socket session forwarder.
+///
+/// It emulates the behavior of SocketSessionForwarder without involving
+/// network communication, and allowing the tester to customize the behavior
+/// and to examine forwarded data afterwards.
+class MockSocketSessionForwarder :
+ public isc::util::io::BaseSocketSessionForwarder
+{
+public:
+ MockSocketSessionForwarder() :
+ is_connected_(false), connect_ok_(true), push_ok_(true),
+ close_ok_(true)
+ {}
+
+ virtual void connectToReceiver() {
+ if (!connect_ok_) {
+ isc_throw(isc::util::io::SocketSessionError, "socket session "
+ "forwarding connection disabled for test");
+ }
+ if (is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate connect");
+ }
+ is_connected_ = true;
+ }
+ virtual void close() {
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate close");
+ }
+ is_connected_ = false;
+ }
+
+ // Pushing a socket session. It copies the given session data
+ // so that the test code can check the values later via the getter
+ // methods. Complete deep copy will be created, so the caller doesn't
+ // have to keep the parameters valid after the call to this method.
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+ {
+ if (!push_ok_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session forwarding is disabled for test");
+ }
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session is being pushed before connected");
+ }
+
+ // Copy parameters for later checks
+ pushed_sock_ = sock;
+ pushed_family_ = family;
+ pushed_type_ = type;
+ pushed_protocol_ = protocol;
+ assert(io::internal::getSALength(local_end) <=
+ sizeof(pushed_local_end_ss_));
+ std::memcpy(&pushed_local_end_ss_, &local_end,
+ io::internal::getSALength(local_end));
+ assert(io::internal::getSALength(remote_end) <=
+ sizeof(pushed_remote_end_ss_));
+ std::memcpy(&pushed_remote_end_ss_, &remote_end,
+ io::internal::getSALength(remote_end));
+ pushed_data_.resize(data_len);
+ std::memcpy(&pushed_data_[0], data, data_len);
+ }
+
+ // Allow the test code to check if the connection is established.
+ bool isConnected() const { return (is_connected_); }
+
+ // Allow the test code to customize the forwarder behavior wrt whether
+ // a specific operation should succeed or fail.
+ void disableConnect() { connect_ok_ = false; }
+ void enableConnect() { connect_ok_ = true; }
+ void disableClose() { close_ok_ = false; }
+ void disablePush() { push_ok_ = false; }
+ void enablePush() { push_ok_ = true; }
+
+ // Read-only accessors to recorded parameters to the previous successful
+ // call to push(). Return values are undefined if there has been no
+ // successful call to push().
+ // Note that we use convertSockAddr() to convert sockaddr_storage to
+ // sockaddr. It should be safe since we use the storage in its literal
+ // sense; it was originally filled with the binary image of another
+ // sockaddr structure, and we are going to return the image opaquely
+ // as a sockaddr structure without touching the data.
+ int getPushedSock() const { return (pushed_sock_); }
+ int getPushedFamily() const { return (pushed_family_); }
+ int getPushedType() const { return (pushed_type_); }
+ int getPushedProtocol() const { return (pushed_protocol_); }
+ const struct sockaddr& getPushedLocalend() const {
+ return (*io::internal::convertSockAddr(&pushed_local_end_ss_));
+ }
+ const struct sockaddr& getPushedRemoteend() const {
+ return (*io::internal::convertSockAddr(&pushed_remote_end_ss_));
+ }
+ const std::vector<uint8_t>& getPushedData() const {
+ return (pushed_data_);
+ }
+
+private:
+ bool is_connected_;
+ bool connect_ok_;
+ bool push_ok_;
+ bool close_ok_;
+ int pushed_sock_;
+ int pushed_family_;
+ int pushed_type_;
+ int pushed_protocol_;
+ struct sockaddr_storage pushed_local_end_ss_;
+ struct sockaddr_storage pushed_remote_end_ss_;
+ std::vector<uint8_t> pushed_data_;
+};
+
+} // end of unittests
+} // end of util
+} // end of isc
+#endif // __UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/xfr/xfrout_client.cc b/src/lib/xfr/xfrout_client.cc
index 227ffc4..af0c1b5 100644
--- a/src/lib/xfr/xfrout_client.cc
+++ b/src/lib/xfr/xfrout_client.cc
@@ -82,8 +82,14 @@ XfroutClient::sendXfroutRequestInfo(const int tcp_sock,
// TODO: this shouldn't be blocking send, even though it's unlikely to
// block.
- // converting the 16-bit word to network byte order.
- const uint8_t lenbuf[2] = { msg_len >> 8, msg_len & 0xff };
+ // Converting the 16-bit word to network byte order.
+
+ // Splitting msg_len below performs something called a 'narrowing
+ // conversion' (conversion of uint16_t to uint8_t). C++0x (and GCC
+ // 4.7) requires explicit casting when a narrowing conversion is
+ // performed. For reference, see 8.5.4/6 of n3225.
+ const uint8_t lenbuf[2] = { static_cast<uint8_t>(msg_len >> 8),
+ static_cast<uint8_t>(msg_len & 0xff) };
if (send(impl_->socket_.native(), lenbuf, sizeof(lenbuf), 0) !=
sizeof(lenbuf)) {
isc_throw(XfroutError,
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
index 1ab506d..20a28fc 100644
--- a/tests/lettuce/features/bindctl_commands.feature
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -109,7 +109,7 @@ Feature: control with bindctl
# nested_command contains another execute script
When I send bind10 the command execute file data/commands/nested
last bindctl output should contain shouldshow
- last bindctl output should not contain Error
+ last bindctl output should not contain Error
# show commands from a file
When I send bind10 the command execute file data/commands/bad_command show
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index bbad595..656836d 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -1,4 +1,8 @@
-SUBDIRS = .
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -8,5 +12,10 @@ if USE_STATIC_LINK
AM_LDFLAGS += -static
endif
+lib_LTLIBRARIES = libperfdhcp++.la
+libperfdhcp___la_SOURCES = command_options.cc command_options.h
+libperfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+libperfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+
pkglibexec_PROGRAMS = perfdhcp
perfdhcp_SOURCES = perfdhcp.c
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
new file mode 100644
index 0000000..7b62076
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -0,0 +1,681 @@
+// Copyright (C) 2011 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "exceptions/exceptions.h"
+
+#include "command_options.h"
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace perfdhcp {
+
+CommandOptions&
+CommandOptions::instance() {
+ static CommandOptions options;
+ return (options);
+}
+
+void
+CommandOptions::reset() {
+ // Default mac address used in DHCP messages
+ // if -b mac=<mac-address> was not specified
+ uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
+
+ // Default packet drop time if -D<drop-time> parameter
+ // was not specified
+ double dt[2] = { 1., 1. };
+
+ // We don't use constructor initialization list because we
+ // will need to reset all members many times to perform unit tests
+ ipversion_ = 0;
+ exchange_mode_ = DORA_SARR;
+ rate_ = 0;
+ report_delay_ = 0;
+ clients_num_ = 0;
+ mac_prefix_.assign(mac, mac + 6);
+ base_.resize(0);
+ num_request_.resize(0);
+ period_ = 0;
+ drop_time_set_ = 0;
+ drop_time_.assign(dt, dt + 2);
+ max_drop_.clear();
+ max_pdrop_.clear();
+ localname_.clear();
+ is_interface_ = false;
+ preload_ = 0;
+ aggressivity_ = 1;
+ local_port_ = 0;
+ seeded_ = false;
+ seed_ = 0;
+ broadcast_ = false;
+ rapid_commit_ = false;
+ use_first_ = false;
+ template_file_.clear();
+ rnd_offset_.clear();
+ xid_offset_.clear();
+ elp_offset_ = -1;
+ sid_offset_ = -1;
+ rip_offset_ = -1;
+ diags_.clear();
+ wrapped_.clear();
+ server_name_.clear();
+}
+
+void
+CommandOptions::parse(int argc, char** const argv) {
+ // Reset internal variables used by getopt
+ // to eliminate undefined behavior when
+ // parsing different command lines multiple times
+ optind = 1;
+ opterr = 0;
+
+ // Reset values of class members
+ reset();
+
+ initialize(argc, argv);
+ validate();
+}
+
+void
+CommandOptions::initialize(int argc, char** argv) {
+ char opt = 0; // Subsequent options returned by getopt()
+ std::string drop_arg; // Value of -D<value>argument
+ size_t percent_loc = 0; // Location of % sign in -D<value>
+ double drop_percent = 0; // % value (1..100) in -D<value%>
+ int num_drops = 0; // Max number of drops specified in -D<value>
+ int num_req = 0; // Max number of dropped requests in -n<max-drops>
+ int offset_arg = 0; // Temporary variable holding offset arguments
+ std::string sarg; // Temporary variable for string args
+
+ // In this section we collect argument values from command line
+ // they will be tuned and validated elsewhere
+ while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
+ switch (opt) {
+ case 'v':
+ version();
+ return;
+
+ case '1':
+ use_first_ = true;
+ break;
+
+ case '4':
+ check(ipversion_ == 6, "IP version already set to 6");
+ ipversion_ = 4;
+ break;
+
+ case '6':
+ check(ipversion_ == 4, "IP version already set to 4");
+ ipversion_ = 6;
+ break;
+
+ case 'a':
+ aggressivity_ = positiveInteger("value of aggressivity: -a<value> must be a positive integer");
+ break;
+
+ case 'b':
+ check(base_.size() > 3, "-b<value> already specified, unexpected occurence of 5th -b<value>");
+ base_.push_back(optarg);
+ decodeBase(base_.back());
+ break;
+
+ case 'B':
+ broadcast_ = true;
+ break;
+
+ case 'c':
+ rapid_commit_ = true;
+ break;
+
+ case 'd':
+ check(drop_time_set_ > 1, "maximum number of drops already specified, "
+ "unexpected 3rd occurence of -d<value>");
+ try {
+ drop_time_[drop_time_set_] = boost::lexical_cast<double>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop time: -d<value> must be positive number");
+ }
+ check(drop_time_[drop_time_set_] <= 0., "drop-time must be a positive number");
+ drop_time_set_ = true;
+ break;
+
+ case 'D':
+ drop_arg = std::string(optarg);
+ percent_loc = drop_arg.find('%');
+ check(max_pdrop_.size() > 1 || max_drop_.size() > 1, "values of maximum drops: -D<value> already "
+ "specified, unexpected 3rd occurence of -D,value>");
+ if ((percent_loc) != std::string::npos) {
+ try {
+ drop_percent = boost::lexical_cast<double>(drop_arg.substr(0, percent_loc));
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop percentage: -D<value%> must be 0..100");
+ }
+ check((drop_percent <= 0) || (drop_percent >= 100),
+ "value of drop percentage: -D<value%> must be 0..100");
+ max_pdrop_.push_back(drop_percent);
+ } else {
+ num_drops = positiveInteger("value of max drops number: -d<value> must be a positive integer");
+ max_drop_.push_back(num_drops);
+ }
+ break;
+
+ case 'E':
+ elp_offset_ = nonNegativeInteger("value of time-offset: -E<value> must not be a negative integer");
+ break;
+
+ case 'h':
+ usage();
+ return;
+
+ case 'i':
+ exchange_mode_ = DO_SA;
+ break;
+
+ case 'I':
+ rip_offset_ = positiveInteger("value of ip address offset: -I<value> must be a positive integer");
+ break;
+
+ case 'l':
+ localname_ = std::string(optarg);
+ break;
+
+ case 'L':
+ local_port_ = nonNegativeInteger("value of local port: -L<value> must not be a negative integer");
+ check(local_port_ > static_cast<int>(std::numeric_limits<uint16_t>::max()),
+ "local-port must be lower than " +
+ boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max()));
+ break;
+
+ case 'n':
+ num_req = positiveInteger("value of num-request: -n<value> must be a positive integer");
+ if (num_request_.size() >= 2) {
+ isc_throw(isc::InvalidParameter,"value of maximum number of requests: -n<value> "
+ "already specified, unexpected 3rd occurence of -n<value>");
+ }
+ num_request_.push_back(num_req);
+ break;
+
+ case 'O':
+ if (rnd_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of random offset: -O<value> must be greater than 3");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "random offsets already specified, unexpected 3rd occurence of -O<value>");
+ }
+ check(offset_arg < 3, "value of random random-offset: -O<value> must be greater than 3 ");
+ rnd_offset_.push_back(offset_arg);
+ break;
+
+ case 'p':
+ period_ = positiveInteger("value of test period: -p<value> must be a positive integer");
+ break;
+
+ case 'P':
+ preload_ = nonNegativeInteger("number of preload packets: -P<value> must not be "
+ "a negative integer");
+ break;
+
+ case 'r':
+ rate_ = positiveInteger("value of rate: -r<value> must be a positive integer");
+ break;
+
+ case 'R':
+ initClientsNum();
+ break;
+
+ case 's':
+ seed_ = static_cast<unsigned int>
+ (nonNegativeInteger("value of seed: -s <seed> must be non-negative integer"));
+ seeded_ = seed_ > 0 ? true : false;
+ break;
+
+ case 'S':
+ sid_offset_ = positiveInteger("value of server id offset: -S<value> must be a positive integer");
+ break;
+
+ case 't':
+ report_delay_ = positiveInteger("value of report delay: -t<value> must be a positive integer");
+ break;
+
+ case 'T':
+ if (template_file_.size() < 2) {
+ sarg = nonEmptyString("template file name not specified, expected -T<filename>");
+ template_file_.push_back(sarg);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "template files are already specified, unexpected 3rd -T<filename> occurence");
+ }
+ break;
+
+ case 'w':
+ wrapped_ = nonEmptyString("command for wrapped mode: -w<command> must be specified");
+ break;
+
+ case 'x':
+ diags_ = nonEmptyString("value of diagnostics selectors: -x<value> must be specified");
+ break;
+
+ case 'X':
+ if (xid_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of transaction id: -X<value> must be a positive integer");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "transaction ids already specified, unexpected 3rd -X<value> occurence");
+ }
+ xid_offset_.push_back(offset_arg);
+ break;
+
+ default:
+ isc_throw(isc::InvalidParameter, "unknown command line option");
+ }
+ }
+
+ // If the IP version was not specified in the
+ // command line, assume IPv4.
+ if (ipversion_ == 0) {
+ ipversion_ = 4;
+ }
+
+ // If template packet files specified for both DISCOVER/SOLICIT
+ // and REQUEST/REPLY exchanges make sure we have transaction id
+ // and random duid offsets for both exchanges. We will duplicate
+ // value specified as -X<value> and -R<value> for second
+ // exchange if user did not specified otherwise.
+ if (template_file_.size() > 1) {
+ if (xid_offset_.size() == 1) {
+ xid_offset_.push_back(xid_offset_[0]);
+ }
+ if (rnd_offset_.size() == 1) {
+ rnd_offset_.push_back(rnd_offset_[0]);
+ }
+ }
+
+ // Get server argument
+ // NoteFF02::1:2 and FF02::1:3 are defined in RFC3315 as
+ // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers
+ // addresses
+ check(optind < argc -1, "extra arguments?");
+ if (optind == argc - 1) {
+ server_name_ = argv[optind];
+ // Decode special cases
+ if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) {
+ broadcast_ = 1;
+ // 255.255.255.255 is IPv4 broadcast address
+ server_name_ = "255.255.255.255";
+ } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) {
+ server_name_ = "FF02::1:2";
+ } else if ((ipversion_ == 6) && (server_name_.compare("servers") == 0)) {
+ server_name_ = "FF05::1:3";
+ }
+ }
+
+ // TODO handle -l option with IfaceManager when it is created
+}
+
+void
+CommandOptions::initClientsNum() {
+ const std::string errmsg = "value of -R <value> must be non-negative integer";
+
+ // Declare clients_num as as 64-bit signed value to
+ // be able to detect negative values provided
+ // by user. We would not detect negative values
+ // if we casted directly to unsigned value.
+ long long clients_num = 0;
+ try {
+ clients_num = boost::lexical_cast<long long>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg.c_str());
+ }
+ check(clients_num < 0, errmsg);
+ try {
+ clients_num_ = boost::lexical_cast<uint32_t>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+void
+CommandOptions::decodeBase(const std::string& base) {
+ std::string b(base);
+ boost::algorithm::to_lower(b);
+
+ // Currently we only support mac and duid
+ if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
+ decodeMac(b);
+ } else if (b.substr(0, 5) == "duid=") {
+ decodeDuid(b);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "base value not provided as -b<value>, expected -b mac=<mac> or -b duid=<duid>");
+ }
+}
+
+void
+CommandOptions::decodeMac(const std::string& base) {
+ // Strip string from mac=
+ size_t found = base.find('=');
+ static const char* errmsg = "expected -b<base> format for mac address is -b mac=00::0C::01::02::03::04";
+ check(found == std::string::npos, errmsg);
+
+ // Decode mac address to vector of uint8_t
+ std::istringstream s1(base.substr(found + 1));
+ std::string token;
+ mac_prefix_.clear();
+ // Get pieces of MAC address separated with : (or even ::)
+ while (std::getline(s1, token, ':')) {
+ unsigned int ui = 0;
+ // Convert token to byte value using std::istringstream
+ if (token.length() > 0) {
+ try {
+ // Do actual conversion
+ ui = convertHexString(token);
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in MAC provided");
+
+ }
+ // If conversion succeeded store byte value
+ mac_prefix_.push_back(ui);
+ }
+ }
+ // MAC address must consist of 6 octets, otherwise it is invalid
+ check(mac_prefix_.size() != 6, errmsg);
+}
+
+void
+CommandOptions::decodeDuid(const std::string& base) {
+ // Strip argument from duid=
+ size_t found = base.find('=');
+ check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
+ std::string b = base.substr(found + 1);
+
+ // DUID must have even number of digits and must not be longer than 64 bytes
+ check(b.length() & 1, "odd number of hexadecimal digits in duid");
+ check(b.length() > 128, "duid too large");
+ check(b.length() == 0, "no duid specified");
+
+ // Turn pairs of hexadecimal digits into vector of octets
+ for (int i = 0; i < b.length(); i += 2) {
+ unsigned int ui = 0;
+ try {
+ // Do actual conversion
+ ui = convertHexString(b.substr(i, 2));
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in DUID provided, exepected hex digits");
+ }
+ duid_prefix_.push_back(static_cast<uint8_t>(ui));
+ }
+}
+
+uint8_t
+CommandOptions::convertHexString(const std::string& text) const {
+ unsigned int ui = 0;
+ // First, check if we are dealing with hexadecimal digits only
+ for (int i = 0; i < text.length(); ++i) {
+ if (!std::isxdigit(text[i])) {
+ isc_throw(isc::InvalidParameter,
+ "The following digit: " << text[i] << " in "
+ << text << "is not hexadecimal");
+ }
+ }
+ // If we are here, we have valid string to convert to octet
+ std::istringstream text_stream(text);
+ text_stream >> std::hex >> ui >> std::dec;
+ // Check if for some reason we have overflow - this should never happen!
+ if (ui > 0xFF) {
+ isc_throw(isc::InvalidParameter, "Can't convert more than two hex digits to byte");
+ }
+ return ui;
+}
+
+void
+CommandOptions::validate() const {
+ check((getIpVersion() != 4) && (isBroadcast() != 0),
+ "-B is not compatible with IPv6 (-6)");
+ check((getIpVersion() != 6) && (isRapidCommit() != 0),
+ "-6 (IPv6) must be set to use -c");
+ check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
+ "second -n<num-request> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.),
+ "second -d<drop-time> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) &&
+ ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
+ "second -D<max-drop> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (isUseFirst()),
+ "-1 is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
+ "second -T<template-file> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
+ "second -X<xid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
+ "second -O<random-offset is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
+ "-E<time-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
+ "-S<srvid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
+ "-I<ip-offset> is not compatible with -i\n");
+ check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
+ "-i must be set to use -c\n");
+ check((getRate() == 0) && (getReportDelay() != 0),
+ "-r<rate> must be set to use -t<report>\n");
+ check((getRate() == 0) && (getNumRequests().size() > 0),
+ "-r<rate> must be set to use -n<num-request>\n");
+ check((getRate() == 0) && (getPeriod() != 0),
+ "-r<rate> must be set to use -p<test-period>\n");
+ check((getRate() == 0) &&
+ ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
+ "-r<rate> must be set to use -D<max-drop>\n");
+ check((getTemplateFiles().size() < getTransactionIdOffset().size()),
+ "-T<template-file> must be set to use -X<xid-offset>\n");
+ check((getTemplateFiles().size() < getRandomOffset().size()),
+ "-T<template-file> must be set to use -O<random-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
+ "second/request -T<template-file> must be set to use -E<time-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -S<srvid-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -I<ip-offset>\n");
+
+}
+
+void
+CommandOptions::check(bool condition, const std::string& errmsg) const {
+ // The same could have been done with macro or just if statement but
+ // we prefer functions to macros here
+ if (condition) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::positiveInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value <= 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::nonNegativeInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value < 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+std::string
+CommandOptions::nonEmptyString(const std::string& errmsg) const {
+ std::string sarg = optarg;
+ if (sarg.length() == 0) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+ return sarg;
+}
+
+void
+CommandOptions::usage() const {
+ fprintf(stdout, "%s",
+"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+" [-n<num-request>] [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+" [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+" [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+" [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+" [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+" [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+"\n"
+"The [server] argument is the name/address of the DHCP server to\n"
+"contact. For DHCPv4 operation, exchanges are initiated by\n"
+"transmitting a DHCP DISCOVER to this address.\n"
+"\n"
+"For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
+"SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
+"can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
+"multicast address FF02::1:2), or the special name 'servers' to refer\n"
+"to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
+"argument is optional only in the case that -l is used to specify an\n"
+"interface, in which case [server] defaults to 'all'.\n"
+"\n"
+"The default is to perform a single 4-way exchange, effectively pinging\n"
+"the server.\n"
+"The -r option is used to set up a performance test, without\n"
+"it exchanges are initiated as fast as possible.\n"
+"\n"
+"Options:\n"
+"-1: Take the server-ID option from the first received message.\n"
+"-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
+"-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
+"-a<aggressivity>: When the target sending rate is not yet reached,\n"
+" control how many exchanges are initiated before the next pause.\n"
+"-b<base>: The base mac, duid, IP, etc, used to simulate different\n"
+" clients. This can be specified multiple times, each instance is\n"
+" in the <type>=<value> form, for instance:\n"
+" (and default) mac=00:0c:01:02:03:04.\n"
+"-d<drop-time>: Specify the time after which a request is treated as\n"
+" having been lost. The value is given in seconds and may contain a\n"
+" fractional component. The default is 1 second.\n"
+"-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
+" elapsed-time option in the (second/request) template.\n"
+" The value 0 disables it.\n"
+"-h: Print this help.\n"
+"-i: Do only the initial part of an exchange: DO or SA, depending on\n"
+" whether -6 is given.\n"
+"-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
+" option / (DHCPv6) IA_NA option in the (second/request) template.\n"
+"-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
+" hostname/address to use when communicating with the server. By\n"
+" default, the interface address through which traffic would\n"
+" normally be routed to the server is used.\n"
+" For DHCPv6 operation, specify the name of the network interface\n"
+" via which exchanges are initiated.\n"
+"-L<local-port>: Specify the local port to use\n"
+" (the value 0 means to use the default).\n"
+"-O<random-offset>: Offset of the last octet to randomize in the template.\n"
+"-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
+"-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
+" exchanges per second. A periodic report is generated showing the\n"
+" number of exchanges which were not completed, as well as the\n"
+" average response latency. The program continues until\n"
+" interrupted, at which point a final report is generated.\n"
+"-R<range>: Specify how many different clients are used. With 1\n"
+" (the default), all requests seem to come from the same client.\n"
+"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
+"-S<srvid-offset>: Offset of the server-ID option in the\n"
+" (second/request) template.\n"
+"-T<template-file>: The name of a file containing the template to use\n"
+" as a stream of hexadecimal digits.\n"
+"-v: Report the version number of this program.\n"
+"-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
+" the program.\n"
+"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
+" <diagnostic-selector> is a string of single-keywords specifying\n"
+" the operations for which verbose output is desired. The selector\n"
+" keyletters are:\n"
+" * 'a': print the decoded command line arguments\n"
+" * 'e': print the exit reason\n"
+" * 'i': print rate processing details\n"
+" * 'r': print randomization details\n"
+" * 's': print first server-id\n"
+" * 't': when finished, print timers of all successful exchanges\n"
+" * 'T': when finished, print templates\n"
+"-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
+"\n"
+"DHCPv4 only options:\n"
+"-B: Force broadcast handling.\n"
+"\n"
+"DHCPv6 only options:\n"
+"-c: Add a rapid commit option (exchanges will be SA).\n"
+"\n"
+"The remaining options are used only in conjunction with -r:\n"
+"\n"
+"-D<max-drop>: Abort the test if more than <max-drop> requests have\n"
+" been dropped. Use -D0 to abort if even a single request has been\n"
+" dropped. If <max-drop> includes the suffix '%', it specifies a\n"
+" maximum percentage of requests that may be dropped before abort.\n"
+" In this case, testing of the threshold begins after 10 requests\n"
+" have been expected to be received.\n"
+"-n<num-request>: Initiate <num-request> transactions. No report is\n"
+" generated until all transactions have been initiated/waited-for,\n"
+" after which a report is generated and the program terminates.\n"
+"-p<test-period>: Send requests for the given test period, which is\n"
+" specified in the same manner as -d. This can be used as an\n"
+" alternative to -n, or both options can be given, in which case the\n"
+" testing is completed when either limit is reached.\n"
+"-t<report>: Delay in seconds between two periodic reports.\n"
+"\n"
+"Errors:\n"
+"- tooshort: received a too short message\n"
+"- orphans: received a message which doesn't match an exchange\n"
+" (duplicate, late or not related)\n"
+"- locallimit: reached to local system limits when sending a message.\n"
+"\n"
+"Exit status:\n"
+"The exit status is:\n"
+"0 on complete success.\n"
+"1 for a general error.\n"
+"2 if an error is found in the command line arguments.\n"
+"3 if there are no general failures in operation, but one or more\n"
+" exchanges are not successfully completed.\n");
+}
+
+void
+CommandOptions::version() const {
+ fprintf(stdout, "version 0.01\n");
+}
+
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h
new file mode 100644
index 0000000..033d29a
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.h
@@ -0,0 +1,412 @@
+// Copyright (C) 2011 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 __COMMAND_OPTIONS_H
+#define __COMMAND_OPTIONS_H
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Command Options
+///
+/// This class is responsible for parsing the command-line and storing the
+/// specified options.
+///
+class CommandOptions : public boost::noncopyable {
+public:
+ /// 2-way (cmd line param -i) or 4-way exchanges
+ enum ExchangeMode {
+ DO_SA,
+ DORA_SARR
+ };
+
+ /// CommandOptions is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// \return the only existing instance of command options
+ static CommandOptions& instance();
+
+ /// \brief Reset to defaults
+ ///
+ /// Reset data members to default values. This is specifically
+ /// useful when unit tests are performed using different
+ /// command line options.
+ void reset();
+
+ /// \brief Parse command line
+ ///
+ /// Parses the command line and stores the selected options
+ /// in class data members.
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if parse fails
+ void parse(int argc, char** const argv);
+
+ /// \brief Returns IP version
+ ///
+ /// \return IP version to be used
+ uint8_t getIpVersion() const { return ipversion_; }
+
+ /// \brief Returns packet exchange mode
+ ///
+ /// \return packet exchange mode
+ ExchangeMode getExchangeMode() const { return exchange_mode_; }
+
+ /// \brief Returns echange rate
+ ///
+ /// \return exchange rate per second
+ int getRate() const { return rate_; }
+
+ /// \brief Returns delay between two performance reports
+ ///
+ /// \return delay between two consecutive performance reports
+ int getReportDelay() const { return report_delay_; }
+
+ /// \brief Returns number of simulated clients
+ ///
+ /// \return number of simulated clients
+ uint32_t getClientsNum() const { return clients_num_; }
+
+ /// \brief Returns MAC address prefix
+ ///
+ /// \ return MAC address prefix to simulate different clients
+ std::vector<uint8_t> getMacPrefix() const { return mac_prefix_; }
+
+ /// \brief Returns DUID prefix
+ ///
+ /// \return DUID prefix to simulate different clients
+ std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; }
+
+ /// \brief Returns base values
+ ///
+ /// \return all base values specified
+ std::vector<std::string> getBase() const { return base_; }
+
+ /// \brief Returns maximum number of exchanges
+ ///
+ /// \return number of exchange requests before test is aborted
+ std::vector<int> getNumRequests() const { return num_request_; }
+
+ /// \brief Returns test period
+ ///
+ /// \return test period before it is aborted
+ int getPeriod() const { return period_; }
+
+ /// \brief Returns drop time
+ ///
+ /// The method returns maximum time elapsed from
+ /// sending the packet before it is assumed dropped.
+ ///
+ /// \return return time before request is assumed dropped
+ std::vector<double> getDropTime() const { return drop_time_; }
+
+ /// \brief Returns maximum drops number
+ ///
+ /// Returns maximum number of packet drops before
+ /// aborting a test.
+ ///
+ /// \return maximum number of dropped requests
+ std::vector<int> getMaxDrop() const { return max_drop_; }
+
+ /// \brief Returns maximal percentage of drops
+ ///
+ /// Returns maximal percentage of packet drops
+ /// before aborting a test.
+ ///
+ /// \return maximum percentage of lost requests
+ std::vector<double> getMaxDropPercentage() const { return max_pdrop_; }
+
+ /// \brief Returns local address or interface name
+ ///
+ /// \return local address or interface name
+ std::string getLocalName() const { return localname_; }
+
+ /// \brief Checks if interface name was used
+ ///
+ /// The method checks if interface name was used
+ /// rather than address.
+ ///
+ /// \return true if interface name was used
+ bool isInterface() const { return is_interface_; }
+
+ /// \brief Returns number of preload exchanges
+ ///
+ /// \return number of preload exchanges
+ int getPreload() const { return preload_; }
+
+ /// \brief Returns aggressivity value
+ ///
+ /// \return aggressivity value
+ int getAggressivity() const { return aggressivity_; }
+
+ /// \brief Returns local port number
+ ///
+ /// \return local port number
+ int getLocalPort() const { return local_port_; }
+
+ /// \brief Checks if seed provided
+ ///
+ /// \return true if seed was provided
+ bool isSeeded() const { return seeded_; }
+
+ /// \brief Returns radom seed
+ ///
+ /// \return random seed
+ uint32_t getSeed() const { return seed_; }
+
+ /// \brief Checks if broadcast address is to be used
+ ///
+ /// \return true if broadcast address is to be used
+ bool isBroadcast() const { return broadcast_; }
+
+ /// \brief Check if rapid commit option used
+ ///
+ /// \return true if rapid commit option is used
+ bool isRapidCommit() const { return rapid_commit_; }
+
+ /// \brief Check if server-ID to be taken from first package
+ ///
+ /// \return true if server-iD to be taken from first package
+ bool isUseFirst() const { return use_first_; }
+
+ /// \brief Returns template file names
+ ///
+ /// \return template file names
+ std::vector<std::string> getTemplateFiles() const { return template_file_; }
+
+ /// brief Returns template offsets for xid
+ ///
+ /// \return template offsets for xid
+ std::vector<int> getTransactionIdOffset() const { return xid_offset_; }
+
+ /// \brief Returns template offsets for rnd
+ ///
+ /// \return template offsets for rnd
+ std::vector<int> getRandomOffset() const { return rnd_offset_; }
+
+ /// \brief Returns template offset for elapsed time
+ ///
+ /// \return template offset for elapsed time
+ int getElapsedTimeOffset() const { return elp_offset_; }
+
+ /// \brief Returns template offset for server-ID
+ ///
+ /// \return template offset for server-ID
+ int getServerIdOffset() const { return sid_offset_; }
+
+ /// \brief Returns template offset for requested IP
+ ///
+ /// \return template offset for requested IP
+ int getRequestedIpOffset() const { return rip_offset_; }
+
+ /// \brief Returns diagnostic selectors
+ ///
+ /// \return diagnostics selector
+ std::string getDiags() const { return diags_; }
+
+ /// \brief Returns wrapped command
+ ///
+ /// \return wrapped command (start/stop)
+ std::string getWrapped() const { return wrapped_; }
+
+ /// \brief Returns server name
+ ///
+ /// \return server name
+ std::string getServerName() const { return server_name_; }
+
+ /// \brief Print usage
+ ///
+ /// Prints perfdhcp usage
+ void usage() const;
+
+ /// \brief Print program version
+ ///
+ /// Prints perfdhcp version
+ void version() const;
+
+private:
+
+ /// \brief Default Constructor
+ ///
+ /// Private constructor as this is a singleton class.
+ /// Use CommandOptions::instance() to get instance of it.
+ CommandOptions() {
+ reset();
+ }
+
+ /// \brief Initializes class members based command line
+ ///
+ /// Reads each command line parameter and sets class member values
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if command line options initialization fails
+ void initialize(int argc, char** argv);
+
+ /// \brief Validates initialized options
+ ///
+ /// \throws isc::InvalidParameter if command line validation fails
+ void validate() const;
+
+ /// \brief Throws !InvalidParameter exception if condition is true
+ ///
+ /// Convenience function that throws an InvalidParameter exception if
+ /// the condition argument is true
+ ///
+ /// \param condition Condition to be checked
+ /// \param errmsg Error message in exception
+ /// \throws isc::InvalidParameter if condition argument true
+ inline void check(bool condition, const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to positive integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int positiveInteger(const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to non-negative integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int nonNegativeInteger(const std::string& errmsg) const;
+
+ /// \brief Returns command line string if it is not empty
+ ///
+ /// \param errmsg Error message if string is empty
+ /// \throw InvalidParameter if string is empty
+ std::string nonEmptyString(const std::string& errmsg) const;
+
+ /// \brief Set number of clients
+ ///
+ /// Interprets the getopt() "opt" global variable as the number of clients
+ /// (a non-negative number). This value is specified by the "-R" switch.
+ ///
+ /// \throw InvalidParameter if -R<value> is wrong
+ void initClientsNum();
+
+ /// \brief Decodes base provided with -b<base>
+ ///
+ /// Function decodes argument of -b switch, which
+ /// specifies a base value used to generate unique
+ /// mac or duid values in packets sent to system
+ /// under test.
+ /// The following forms of switch arguments are supported:
+ /// - -b mac=00:01:02:03:04:05
+ /// - -b duid=0F1234 (duid can be up to 128 hex digits)
+ // Function will decode 00:01:02:03:04:05 and/or
+ /// 0F1234 respectively and initialize mac_prefix_
+ /// and/or duid_prefix_ members
+ ///
+ /// \param base Base in string format
+ /// \throws isc::InvalidParameter if base is invalid
+ void decodeBase(const std::string& base);
+
+ /// \brief Decodes base MAC address provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b mac=00:01:02:03:04:05
+ /// The function will decode 00:01:02:03:04:05 initialize mac_prefix_
+ /// class member.
+ /// Provided MAC address is for example only
+ ///
+ /// \param base Base string given as -b mac=00:01:02:03:04:05
+ /// \throws isc::InvalidParameter if mac address is invalid
+ void decodeMac(const std::string& base);
+
+ /// \brief Decodes base DUID provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b duid=0F1234
+ /// The function will decode 0F1234 and initialize duid_prefix_
+ /// class member.
+ /// Provided DUID is for example only.
+ ///
+ /// \param base Base string given as -b duid=0F1234
+ /// \throws isc::InvalidParameter if DUID is invalid
+ void decodeDuid(const std::string& base);
+
+ /// \brief Converts two-digit hexadecimal string to a byte
+ ///
+ /// \param hex_text Hexadecimal string e.g. AF
+ /// \throw isc::InvalidParameter if string does not represent hex byte
+ uint8_t convertHexString(const std::string& hex_text) const;
+
+ uint8_t ipversion_; ///< IP protocol version to be used, expected values are:
+ ///< 4 for IPv4 and 6 for IPv6, default value 0 means "not set"
+ ExchangeMode exchange_mode_; ///< Packet exchange mode (e.g. DORA/SARR)
+ int rate_; ///< Rate in exchange per second
+ int report_delay_; ///< Delay between generation of two consecutive
+ ///< performance reports
+ uint32_t clients_num_; ///< Number of simulated clients (aka randomization range).
+ std::vector<uint8_t> mac_prefix_; ///< MAC address prefix used to generate unique DUIDs
+ ///< for simulated clients.
+ std::vector<uint8_t> duid_prefix_; ///< DUID prefix used to generate unique DUIDs for
+ ///< simulated clients
+ std::vector<std::string> base_; ///< Collection of base values specified with -b<value>
+ ///< options. Supported "bases" are mac=<mac> and duid=<duid>
+ std::vector<int> num_request_; ///< Number of 2 or 4-way exchanges to perform
+ int period_; ///< Test period in seconds
+ uint8_t drop_time_set_; ///< Indicates number of -d<value> parameters specified by user.
+ ///< If this value goes above 2, command line parsing fails.
+ std::vector<double> drop_time_; ///< Time to elapse before request is lost. The fisrt value of
+ ///< two-element vector refers to DO/SA exchanges,
+ ///< second value refers to RA/RR. Default values are { 1, 1 }
+ std::vector<int> max_drop_; ///< Maximum number of drops request before aborting test.
+ ///< First value of two-element vector specifies maximum
+ ///< number of drops for DO/SA exchange, second value
+ ///< specifies maximum number of drops for RA/RR.
+ std::vector<double> max_pdrop_; ///< Maximal percentage of lost requests before aborting test.
+ ///< First value of two-element vector specifies percentage for
+ ///< DO/SA exchanges, second value for RA/RR.
+ std::string localname_; ///< Local address or interface specified with -l<value> option.
+ bool is_interface_; ///< Indicates that specified value with -l<value> is
+ ///< rather interface (not address)
+ int preload_; ///< Number of preload packets. Preload packets are used to
+ ///< initiate communication with server before doing performance
+ ///< measurements.
+ int aggressivity_; ///< Number of exchanges sent before next pause.
+ int local_port_; ///< Local port number (host endian)
+ bool seeded_; ///< Indicates that randomization seed was provided.
+ uint32_t seed_; ///< Randomization seed.
+ bool broadcast_; ///< Indicates that we use broadcast address.
+ bool rapid_commit_; ///< Indicates that we do rapid commit option.
+ bool use_first_; ///< Indicates that we take server id from first received packet.
+ std::vector<std::string> template_file_; ///< Packet template file names. These files store template packets
+ ///< that are used for initiating echanges. Template packets
+ ///< read from files are later tuned with variable data.
+ std::vector<int> xid_offset_; ///< Offset of transaction id in template files. First vector
+ ///< element points to offset for DISCOVER/SOLICIT messages,
+ ///< second element points to trasaction id offset for
+ ///< REQUEST messages
+ std::vector<int> rnd_offset_; ///< Random value offset in templates. Random value offset
+ ///< points to last octet of DUID. Up to 4 last octets of
+ ///< DUID are randomized to simulate differnt clients.
+ int elp_offset_; ///< Offset of elapsed time option in template packet.
+ int sid_offset_; ///< Offset of server id option in template packet.
+ int rip_offset_; ///< Offset of requested ip data in template packet/
+ std::string diags_; ///< String representing diagnostic selectors specified
+ ///< by user with -x<value>.
+ std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected
+ ///< values are start and stop.
+ std::string server_name_; ///< Server name specified as last argument of command line.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
new file mode 100644
index 0000000..c94ecba
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += command_options_unittest.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
new file mode 100644
index 0000000..c92edd0
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -0,0 +1,454 @@
+// Copyright (C) 2011 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 <cstddef>
+#include <stdint.h>
+#include <string>
+#include <gtest/gtest.h>
+
+#include "../command_options.h"
+
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief Test Fixture Class
+///
+/// This test fixture class is used to perform
+/// unit tests on perfdhcp CommandOptions class.
+class CommandOptionsTest : public virtual ::testing::Test
+{
+public:
+ /// \brief Default Constructor
+ CommandOptionsTest() { }
+
+protected:
+ /// \brief Parse command line and cleanup
+ ///
+ /// The method tokenizes command line to array of C-strings,
+ /// parses arguments using CommandOptions class to set
+ /// its data members and de-allocates array of C-strings.
+ ///
+ /// \param cmdline Command line to parse
+ /// \throws std::bad allocation if tokenization failed
+ void process(const std::string& cmdline) {
+ CommandOptions& opt = CommandOptions::instance();
+ int argc = 0;
+ char** argv = tokenizeString(cmdline, &argc);
+ opt.reset();
+ opt.parse(argc, argv);
+ for(int i = 0; i < argc; ++i) {
+ free(argv[i]);
+ argv[i] = NULL;
+ }
+ free(argv);
+ }
+
+ /// \brief Check default initialized values
+ ///
+ /// Check if initialized values are correct
+ void checkDefaults() {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
+ EXPECT_EQ(0, opt.getRate());
+ EXPECT_EQ(0, opt.getReportDelay());
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // default mac
+ uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+
+ EXPECT_EQ(0, opt.getBase().size());
+ EXPECT_EQ(0, opt.getNumRequests().size());
+ EXPECT_EQ(0, opt.getPeriod());
+ for (int i = 0; i < opt.getDropTime().size(); ++i) {
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[i]);
+ }
+ ASSERT_EQ(opt.getMaxDrop().size(), opt.getMaxDropPercentage().size());
+ for (int i = 0; i < opt.getMaxDrop().size(); ++i) {
+ EXPECT_EQ(0, opt.getMaxDrop()[i]);
+ EXPECT_EQ(0, opt.getMaxDropPercentage()[i]);
+ }
+ EXPECT_EQ("", opt.getLocalName());
+ EXPECT_FALSE(opt.isInterface());
+ EXPECT_EQ(0, opt.getPreload());
+ EXPECT_EQ(1, opt.getAggressivity());
+ EXPECT_EQ(0, opt.getLocalPort());
+ EXPECT_FALSE(opt.isSeeded());
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isUseFirst());
+ EXPECT_EQ(0, opt.getTemplateFiles().size());
+ EXPECT_EQ(0, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(0, opt.getRandomOffset().size());
+ EXPECT_GT(0, opt.getElapsedTimeOffset());
+ EXPECT_GT(0, opt.getServerIdOffset());
+ EXPECT_GT(0, opt.getRequestedIpOffset());
+ EXPECT_EQ("", opt.getDiags());
+ EXPECT_EQ("", opt.getWrapped());
+ EXPECT_EQ("", opt.getServerName());
+ }
+
+ /// \brief Split string to array of C-strings
+ ///
+ /// \param s String to split (tokenize)
+ /// \param num Number of tokens returned
+ /// \return array of C-strings (tokens)
+ char** tokenizeString(const std::string& text_to_split, int* num) const {
+ char** results = NULL;
+ // Tokenization with std streams
+ std::stringstream text_stream(text_to_split);
+ // Iterators to be used for tokenization
+ std::istream_iterator<std::string> text_iterator(text_stream);
+ std::istream_iterator<std::string> text_end;
+ // Tokenize string (space is a separator) using begin and end iteratos
+ std::vector<std::string> tokens(text_iterator, text_end);
+
+ if (tokens.size() > 0) {
+ // Allocate array of C-strings where we will store tokens
+ results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
+ if (results == NULL) {
+ throw std::bad_alloc();
+ }
+ // Store tokens in C-strings array
+ for (int i = 0; i < tokens.size(); ++i) {
+ char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
+ strcpy(cs, tokens[i].c_str());
+ results[i] = cs;
+ }
+ // Return number of tokens to calling function
+ if (num != NULL) {
+ *num = tokens.size();
+ }
+ }
+ return results;
+ }
+
+};
+
+TEST_F(CommandOptionsTest, Defaults) {
+ process("perfdhcp");
+ checkDefaults();
+}
+
+TEST_F(CommandOptionsTest, UseFirst) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -B -l ethx");
+ EXPECT_TRUE(opt.isUseFirst());
+}
+TEST_F(CommandOptionsTest, IpVersion) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -l ethx -c -i");
+ EXPECT_EQ(6, opt.getIpVersion());
+ EXPECT_EQ("ethx", opt.getLocalName());
+ EXPECT_TRUE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isBroadcast());
+ process("perfdhcp -4 -B -l ethx");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_TRUE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+
+ // Negative test cases
+ // -4 and -6 must not coexist
+ EXPECT_THROW(process("perfdhcp -4 -6 -l ethx"), isc::InvalidParameter);
+ // -6 and -B must not coexist
+ EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
+ // -c and -4 (default) must not coexist
+ EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Rate) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -4 -r 10 -l ethx");
+ EXPECT_EQ(10, opt.getRate());
+
+ // Negative test cases
+ // Rate must not be 0
+ EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
+ // -r must be specified to use -n, -p and -D
+ EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ReportDelay) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -r 100 -t 17 -l ethx");
+ EXPECT_EQ(17, opt.getReportDelay());
+
+ // Negative test cases
+ // -t must be positive integer
+ EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ClientsNum) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -R 200 -l ethx");
+ EXPECT_EQ(200, opt.getClientsNum());
+ process("perfdhcp -R 0 -l ethx");
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // Negative test cases
+ // Number of clients must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Base) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
+ uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
+ uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
+
+ // Test Mac
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // "3x" is invalid value in MAC address
+ EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter);
+
+ // Test DUID
+ std::vector<uint8_t> v2 = opt.getDuidPrefix();
+ ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
+ EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+ // "t" is invalid digit in DUID
+ EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter);
+
+ // Some more negative test cases
+ // Base is not specified
+ EXPECT_THROW(process("perfdhcp -b -l ethx"), isc::InvalidParameter);
+ // Typo: should be mac= instead of mc=
+ EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, DropTime) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -d 12");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]);
+
+ process("perfdhcp -l ethx -d 2 -d 4.7");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
+
+ // Negative test cases
+ // Drop time must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TimeOffset) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
+ EXPECT_EQ(4, opt.getElapsedTimeOffset());
+
+ // Negative test cases
+ // Argument -E must be used with -T
+ EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
+ // Value in -E not specified
+ EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i"), isc::InvalidParameter);
+ // Value for -E must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ExchangeMode) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i");
+ EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
+ // Offsets can't be used in simple exchanges (-i)
+ EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Offsets) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx -X3 -T file1.x -T file2.x");
+ EXPECT_EQ(2, opt.getRequestedIpOffset());
+ EXPECT_EQ(5, opt.getElapsedTimeOffset());
+ EXPECT_EQ(3, opt.getServerIdOffset());
+ ASSERT_EQ(2, opt.getRandomOffset().size());
+ EXPECT_EQ(30, opt.getRandomOffset()[0]);
+ EXPECT_EQ(30, opt.getRandomOffset()[1]);
+ ASSERT_EQ(2, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(7, opt.getTransactionIdOffset()[0]);
+ EXPECT_EQ(3, opt.getTransactionIdOffset()[1]);
+
+ // Negative test cases
+ // IP offset/IA_NA offset must be positive
+ EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter);
+
+ // TODO - other negative cases
+}
+
+TEST_F(CommandOptionsTest, LocalPort) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -L 2000");
+ EXPECT_EQ(2000, opt.getLocalPort());
+
+ // Negative test cases
+ // Local port must be between 0..65535
+ EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Preload) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -P 3 -l ethx");
+ EXPECT_EQ(3, opt.getPreload());
+
+ // Negative test cases
+ // Number of preload packages must not be negative integer
+ EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Seed) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -P 2 -s 23 -l ethx");
+ EXPECT_EQ(23, opt.getSeed());
+ EXPECT_TRUE(opt.isSeeded());
+
+ process("perfdhcp -6 -P 2 -s 0 -l ethx");
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isSeeded());
+
+ // Negtaive test cases
+ // Seed must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TemplateFiles) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -T file1.x -l ethx");
+ ASSERT_EQ(1, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+
+ process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx");
+ ASSERT_EQ(2, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+ EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T"), isc::InvalidParameter);
+ // Too many template files specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x -T file.x -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Wrapped) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -B -w start -i -l ethx");
+ EXPECT_EQ("start", opt.getWrapped());
+
+ // Negative test cases
+ // Missing command after -w, expected start/stop
+ EXPECT_THROW(process("perfdhcp -B -i -l ethx -w"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Diagnostics) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i -x asTe");
+ EXPECT_EQ("asTe", opt.getDiags());
+
+ // Negative test cases
+ // No diagnostics string specified
+ EXPECT_THROW(process("perfdhcp -l ethx -i -x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Aggressivity) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -a 10 -l 192.168.0.1");
+ EXPECT_EQ(10, opt.getAggressivity());
+
+ // Negative test cases
+ // Aggressivity must be non negative integer
+ EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, MaxDrop) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -D 25 -l ethx -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ process("perfdhcp -D 25 -l ethx -D 15 -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ EXPECT_EQ(15, opt.getMaxDrop()[1]);
+
+ process("perfdhcp -D 15% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ process("perfdhcp -D 15% -D25% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(25, opt.getMaxDropPercentage()[1]);
+ process("perfdhcp -D 1% -D 99% -l ethx -r 10");
+ EXPECT_EQ(1, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
+
+ // Negative test cases
+ // Too many -D<value> options
+ EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3"), isc::InvalidParameter);
+ // Too many -D<value%> options
+ EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10%"), isc::InvalidParameter);
+ // Percentage is out of bounds
+ EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, NumRequest) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -n 1000 -r 10 -l ethx");
+ EXPECT_EQ(1000, opt.getNumRequests()[0]);
+ process("perfdhcp -n 5 -r 10 -n 500 -l ethx");
+ EXPECT_EQ(5, opt.getNumRequests()[0]);
+ EXPECT_EQ(500, opt.getNumRequests()[1]);
+
+ // Negative test cases
+ // Too many -n<value> parameters, expected maximum 2
+ EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20"), isc::InvalidParameter);
+ // Num request must be positive integer
+ EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Period) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -p 120 -l ethx -r 100");
+ EXPECT_EQ(120, opt.getPeriod());
+
+ // Negative test cases
+ // Test period must be positive integer
+ EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter);
+}
diff --git a/tests/tools/perfdhcp/tests/run_unittests.cc b/tests/tools/perfdhcp/tests/run_unittests.cc
new file mode 100644
index 0000000..6eeca75
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009 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 <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (isc::util::unittests::run_all());
+}
More information about the bind10-changes
mailing list