BIND 10 trac1514, updated. fb2001dc826b485c3d86ab3eff689c3f13c5b710 Merge branch 'master' into trac1514 [1514] merge master to trac1514 Conflicts: src/lib/python/isc/ddns/session.py src/lib/python/isc/ddns/tests/session_tests.py
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Jun 11 05:42:05 UTC 2012
The branch, trac1514 has been updated
via fb2001dc826b485c3d86ab3eff689c3f13c5b710 (commit)
via 9b2f2c4d4f87cf7672a3aa8efc25fb73400dce3a (commit)
via fd3ab0734141e732049a939e11cc7353fdce98c0 (commit)
via 6ae5787bbe982a751ed058e7c4ccf77d760627dd (commit)
via 3da2b4dd513ce1108f30a2901bdbab9049845d7e (commit)
via 1592922fd2504f5172fd40e29514372b965e926d (commit)
via 45ea2c37d6f00d7f0cfe2e342a0a2907c032dc54 (commit)
via 68b33af1da81765fe4f390ca3b6d9a8959736e3c (commit)
via f378dbd066722c41f39718cf26ec0282645896e8 (commit)
via f9ec0f7c7b3d60c19b258f2c0d95edb7d4a3e8a1 (commit)
via e33d599164b3d1de0617da52ae2b1153238e11d1 (commit)
via 47965690b14b7567183ee041e5ba302b287141e3 (commit)
via 38292a61825ac30e3a589d8b0922c42ad3437055 (commit)
via 36b9d41e18ceda7768fedfc557346b51105b566d (commit)
via 31d7b2369b79150c8d8db697e489216aca50d2fc (commit)
via 93d4730a6135e573efdcd4fe0780119e5d343093 (commit)
via b4c8cff4f3822d78c4c16ae35a2077596a3888ef (commit)
via 1c8b4bfefebe843d295022c5cce0d60007a56269 (commit)
via b56e5b30bdb16a5553c9c45dbe3dca73bc4613d0 (commit)
via c28fd5d9f54d75c77ffb8d1ef3fdd363952ee694 (commit)
via 52abb6303cfee736542f11e4046bb3c89b58ae63 (commit)
via c8fa08e6366e4ce0df1e932c68efef634eba159b (commit)
via 7130da883f823ce837c10cbf6e216a15e1996e5d (commit)
via 6f8dbd0f299c6c0f97cf24e87116fa32f43c8a0f (commit)
via 41708801415792402d7f83c57aea50dae0550622 (commit)
via ed9fbd276086a6b494f7fd5b573e23f0e467007a (commit)
via 1fdb0ee73a22d8002cbfefe66d70cf22d5d784d8 (commit)
via adca81c006608c1e97b900380ce48834876c2908 (commit)
via ad8d445dd0ba208107eb239405166c5c2070bd8b (commit)
via 2f4b6ea6438119eaee2c06c6d97bb66825a174a6 (commit)
via f71a0c646d703351f989c2705e237fa77705c504 (commit)
via 9e812734e020c04ef076f6b2fc38d3f872a0c361 (commit)
via d3e3aa8bb4c6d409c2af01a72270369eb00cc03a (commit)
via 3e0f47f73941c7c1249ee7fb79a880770d7fb76a (commit)
via fa9af9770151197c95bd8366f608bb7b56751243 (commit)
via 23e49e12b90f0360ad02e5d97dd40db50a52b79d (commit)
via 0010be465624ebd2c72aa41e9a56935e76f3da5c (commit)
via 1bf6cdf0b6217de0bfd33116881d8ca252617d13 (commit)
via 0fd36bf98ca106464ef9de56aadc2d6388cb4c83 (commit)
via e0d617df81281066d044c4137beeb3e32921c0d5 (commit)
via 6719c7e0be950cbeba5612b280d859992bda1d02 (commit)
via eab5b84db6c5a0db2e7cc9c53b5433797905a599 (commit)
via 1b9d39ec504ce53be4a9e9ef731a7a4295669800 (commit)
via 5122d4095a0865413c3f80083ef78a54bc3f7ea6 (commit)
via edadf68c0de62bc2dbdce97ff8372f36fd8bec8d (commit)
via 55330acd18cb29963fc23be6eb59d5a56ab64f9a (commit)
via 11af4fd1af04140c9e64aff58efc5f6661a41cf0 (commit)
via 91164ce5585a7d659f73ba86da2ffcae6db2f5e6 (commit)
via 7987b09972865cf558dbff4489a198162ec8e3c3 (commit)
via 1c0e6f80265bb516f5f965eea9b0edac781cbdea (commit)
via 52edf764154ed08161a4683d6b7b7be5a232face (commit)
via cc84b0a02de65a71d80fac9b6f15533e9b0c6037 (commit)
via f68857bea3699eb23c72ddac04a20aa8f42bd551 (commit)
via 07851523e66fe1e045e953d505b97e2cf7f499fc (commit)
via 6306feefed1a1fc462bfff708a47c5fffb351a51 (commit)
via 8d55fd24318cd72d0a624304dfb7c107813e43cf (commit)
via a0bf4ce38c190728f172ee9e2a335248a0ed7a1d (commit)
via 7d6667db23ab1724f9e30b148456742a46bfc0e1 (commit)
via 02f76675c5728649d65e63c29f0603d465730281 (commit)
via 92cdf7773ea4631eda41a15b5b5b21a4926ff391 (commit)
via bc9715b080230f432420a7796d8ec2c5029e889e (commit)
via 18aa5359405c164ecda151b43efd125028d3a1ef (commit)
via 98c723ce92f98e2982f61fbbb4c12b74c4aed87d (commit)
via c5b7c61747858fb2fb34e62f4f04d4be663f7adb (commit)
via afc0fe7345aa86ba3eaebf1bba8f62db64760c1f (commit)
via 71fcb404d2cf321b39e898ed21c18a27435a6d34 (commit)
via 07c46c2fd50771f47e70436c3d5e7be801091b93 (commit)
via a318db63500413168e1723119de2eef07c969290 (commit)
via 923f3c0b2a848903c0da0866a7943779bc8bd863 (commit)
via fa2c987ffe91e103a839c4a51ed2a26a1ea23691 (commit)
via b9bde80941d4500821c093b6b28e9752b67b7e3f (commit)
via 6b23d728601ee0972f06c803cce44cccad1aadc6 (commit)
via 6e845d8827c0450396f553cedf2a26448ee35fbf (commit)
via c6544e26eec66ec1c07e52c040a2d4638c941bd6 (commit)
via 25789340a677b38451ce4edb4aa2eb389d69d266 (commit)
via 2b2439252c79f6d1bc6ca1dc38def8ff6f2bf8c7 (commit)
via ebbfe90a30f5ed7bb3929f23d0eecb01b05e163e (commit)
via 100ee5216c3f6402ee08756f35c7af01fa363100 (commit)
via 7a70191fed37bff9ceb0fed6992fdc6c96ff257f (commit)
via 2f88c7ad45869921177bf4dcc477dbdd87c8cda6 (commit)
via f89b4428468decd82132b0e0a02f2dc171fd690c (commit)
via 24fb93177f6d50c2713374ef11432510c4da6a0e (commit)
via 6002a070bac4531ece1127f32434b4bc565952d8 (commit)
via 088760558f2321ea60b393b37fba0a672ce9f943 (commit)
via 20a21a87777944fea10cf615b0b54d6e96df00ae (commit)
via 56c895c19be423403c0ddb84f25ea958af9cc371 (commit)
via 60c3dcef1055d54231708188639056248258e716 (commit)
via d6859a67235140aeb14a2224198b85e4d65ed71d (commit)
via 162e15111002d1e857222eb309018818b637fbad (commit)
via 195c7eaef0d31ad0a37ad4bf53621506a7ffe02d (commit)
via 031eaf1e2378242b5ebd2804347cba5843408eab (commit)
via 266ddad13b59fe73124e35af6f3a3c62f0f743f5 (commit)
via e8cabc67f199b59704c0064521ddd6fdaeb438d1 (commit)
via e60af9fa16a6094d2204f27c40a648fae313bdae (commit)
via b128daebb970c0b09680e1d1c485af8dcfc4ab55 (commit)
via 1f12f3cd5bc0b3b39545680c4dd772303c62d43f (commit)
via 3e8b461db077bf467280cec8c6c2a03dd0df0c6c (commit)
via 949d3d2f07686be8228f3fdb44a756e280dfbd85 (commit)
via e04531f57054b71fa83ad6bc8467e31df311d39c (commit)
via 30987308b9623d0cbd4aa010c14e5238dd09e023 (commit)
via 6b3132f71b397e4925b80ed1dc1e3cb834be1294 (commit)
via 865669e98402f25c41f35e3957bdb9190513bf6f (commit)
via 58c0a8ef0152662b581b7a13fecf92ed0b0021e8 (commit)
via 8c38fe10458178dee5a0045ae2bfa3da986d6a0c (commit)
via e007ac3e1fb0595497757b3590c3023c825965d4 (commit)
via fcb63453e3d835fdca05146482d001035cdbd6af (commit)
via cd373315919dd5a20440d27d49c3c0e47ebb3f19 (commit)
via 277be10c905a1b592cc8ddb2ded6f066c619a0f2 (commit)
via 8eb4eb3e63b8f19155ae8f021436c881d75706c6 (commit)
via e72bc121a7f7e4a85f889a6c1c4eec5bafd51325 (commit)
via 075eef9a1fe8c61c69532ce8b9dc05eda102f08d (commit)
via b35d3159e812712e47d59db80ae0115e58cd5c82 (commit)
via 948f12089ccfd8e77c568568ed78153dd926c646 (commit)
via b94f49bf264e058dac14287eca360c82edb0071d (commit)
via f77a1e4539afd8dea0a1db15d46d85b810c5c4ee (commit)
via eacdcb4322f7fc89f35af526768bf42403ad09a6 (commit)
via 2e95197a4b47847b4ec85e83bdb1fb5edf685cd0 (commit)
via c4e3386e7c0b60f46ba4283dfdea069437fc3932 (commit)
via 7c1277b0a6750ab51611971adf77a9c2740511cc (commit)
via 994827bfe0a9b77d3bf724768a2403965cb8ad57 (commit)
via 39ebe9d1fd4d04690162c92ebb640f36fa2586ca (commit)
via 9227b2de28f57892f5570ca0f30f0f6c651922be (commit)
via 1d5bd47c50ca03b87972977bb3bdd8d798148698 (commit)
via 328f8781d99bdf1c78abbee743b85244ac55f6b8 (commit)
via 16fc37014cf72d8ca5b3f9461504be2c69a37702 (commit)
via d575fbaa4083182e8d6602bb042934451ae06c65 (commit)
via bd5494fa3a4aded378c5e2167f6c9e1b7eaf2035 (commit)
via 8b555fcb29378dcb0db476bf8f53804acae8fe77 (commit)
via 93fb5cab3e4a0752e2471931e171937392c71ae0 (commit)
via caa5bb9a9b852631d1548460daef2b93cf48c64c (commit)
via d6451dba2ce9ad4b2cf5ef63ff312136542abe4e (commit)
via 518e4673c218e2833cbeadb1a8b1cb6e8aa04f26 (commit)
via b14e2801e5f173f2eb88cbb5cb6717c1e224de18 (commit)
via 84ed05352881fca810292b827867dcd08f256cbe (commit)
via 227d01775bdb4dab84c97e5858c0812e2aa845c2 (commit)
via 30198a34b9e464ffa2947955e7dd70a191e39682 (commit)
via c0a328b2d94321966d5ebe97dd8f2058d6d76176 (commit)
via b45a54e8ccf0c947b7f3979ed1083260c47be453 (commit)
via 8c455b3a39a588e11a86523056caa9c379b60a67 (commit)
via 31b153df2003831950bf3fcd6a4da782dc579eb1 (commit)
via 10b434be10f3fa900760180feb030907484c9b6f (commit)
via db2fa0cd177d28c49a9c99aabe44bd9c47c6b78d (commit)
via 09e8c9e8c55571196fbd297fda46e95400aa0990 (commit)
via 2bf7dad904d16760987d3a0b27c02bb8a2b50e55 (commit)
via 5d228bec6c0802a6b0ff19618745257c8f5b0af4 (commit)
via 876dd74d15bcaa619602bcb60fc769d541923048 (commit)
via 848be350a53e835929a536d295d0fc3ee12a2376 (commit)
via 1b5e0412294dcf0bc499c4fd288db0f6d88c4ba5 (commit)
via 17f327dc2a155c493dfe265cb6fac7b09a7d8836 (commit)
via 96391341079418eeb6c3dd6f0da85239d19f1749 (commit)
via 9427bd4115b59e4498d13dc0a83fc2953b104266 (commit)
via 588bffd5077a9e6cc741100cdc721b444d80c4ca (commit)
via 106ec83cb515ddcc84834ba336dc39679377a8fd (commit)
via 7d9bb0957c7eeb3d770befc75b9dd33667467269 (commit)
via 9cffd7d9f5dc6c9c2909f9569c2f4c78aa71bdbd (commit)
via 2be06486e504e522f9ef39586fe7fb5947c33872 (commit)
from 5f5ebca253e112f149d67b171ce1c86cbd3d4a1a (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 fb2001dc826b485c3d86ab3eff689c3f13c5b710
Merge: 5f5ebca 9b2f2c4
Author: haikuo zhang <zhanghaikuo at cnnic.cn>
Date: Mon Jun 11 13:40:59 2012 +0800
Merge branch 'master' into trac1514
[1514] merge master to trac1514
Conflicts:
src/lib/python/isc/ddns/session.py
src/lib/python/isc/ddns/tests/session_tests.py
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 23 +
Makefile.am | 2 +
configure.ac | 2 +
src/bin/auth/auth_srv.cc | 5 +-
src/bin/bind10/bind10.8 | 2 +-
src/bin/bind10/bind10.xml | 2 +-
src/bin/bind10/bind10_src.py.in | 24 +
src/bin/bind10/tests/Makefile.am | 1 +
src/bin/bind10/tests/bind10_test.py.in | 35 +
src/bin/bindctl/command_sets.py | 1 -
src/bin/cfgmgr/b10-cfgmgr.8 | 2 +-
src/bin/cfgmgr/b10-cfgmgr.xml | 2 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 1 -
src/bin/cmdctl/tests/Makefile.am | 1 +
src/bin/dbutil/dbutil.py.in | 2 +-
src/bin/dbutil/tests/Makefile.am | 1 +
src/bin/ddns/ddns.py.in | 290 +-
src/bin/ddns/ddns.spec | 24 +-
src/bin/ddns/ddns_messages.mes | 47 +
src/bin/ddns/tests/Makefile.am | 1 +
src/bin/ddns/tests/ddns_test.py | 532 ++-
src/bin/dhcp4/main.cc | 25 +-
src/bin/dhcp4/tests/Makefile.am | 17 +-
src/bin/dhcp4/tests/dhcp4_test.py | 170 +
src/bin/dhcp6/dhcp6_srv.cc | 2 +-
src/bin/dhcp6/main.cc | 23 +-
src/bin/dhcp6/tests/Makefile.am | 5 +-
src/bin/dhcp6/tests/dhcp6_test.py | 159 +-
src/bin/sockcreator/Makefile.am | 2 +-
src/bin/stats/tests/Makefile.am | 1 +
src/lib/asiolink/io_endpoint.h | 4 +-
src/lib/cc/tests/Makefile.am | 2 +-
src/lib/config/module_spec.cc | 2 +-
src/lib/datasrc/database.cc | 36 +-
src/lib/datasrc/datasrc_messages.mes | 11 +-
src/lib/datasrc/memory_datasrc.cc | 2 +-
src/lib/datasrc/rbnode_rrset.h | 2 +-
src/lib/datasrc/tests/database_unittest.cc | 56 +-
src/lib/dns/labelsequence.h | 4 +-
src/lib/dns/message.cc | 6 +-
src/lib/dns/python/message_python.cc | 245 +-
src/lib/dns/python/name_python.cc | 4 +-
src/lib/dns/python/pydnspp_common.h | 5 +
src/lib/dns/python/rrclass_python.cc | 4 +-
src/lib/dns/python/tests/message_python_test.py | 15 +
src/lib/dns/rdata.cc | 2 +-
src/lib/dns/rdata/any_255/tsig_250.cc | 33 +-
src/lib/dns/rdata/ch_3/a_1.cc | 2 +-
.../dns/rdata/generic/detail/nsec3param_common.cc | 1 -
src/lib/dns/rdata/generic/dlv_32769.cc | 2 +-
src/lib/dns/rdata/generic/dnskey_48.cc | 2 +-
src/lib/dns/rdata/generic/ds_43.cc | 2 +-
src/lib/dns/rdata/generic/hinfo_13.cc | 2 +-
src/lib/dns/rdata/generic/nsec3_50.cc | 2 +-
src/lib/dns/rdata/generic/nsec3param_51.cc | 2 +-
src/lib/dns/rdata/generic/nsec_47.cc | 2 +-
src/lib/dns/rdata/generic/opt_41.cc | 2 +-
src/lib/dns/rdata/generic/ptr_12.cc | 2 +-
src/lib/dns/rdata/generic/rrsig_46.cc | 2 +-
src/lib/dns/rdata/generic/soa_6.cc | 2 +-
src/lib/dns/rdata/generic/sshfp_44.cc | 2 +-
src/lib/dns/rdata/hs_4/a_1.cc | 2 +-
src/lib/dns/rdata/in_1/a_1.cc | 2 +-
src/lib/dns/rdata/in_1/aaaa_28.cc | 2 +-
src/lib/dns/rdata/in_1/dhcid_49.cc | 2 +-
src/lib/dns/rdata/in_1/srv_33.cc | 12 +-
src/lib/dns/rrclass.cc | 2 +-
src/lib/dns/rrparamregistry-placeholder.cc | 8 +-
src/lib/dns/rrttl.cc | 2 +-
src/lib/dns/rrtype.cc | 2 +-
src/lib/dns/tests/message_unittest.cc | 2 +
src/lib/log/Makefile.am | 1 +
src/lib/log/compiler/message.cc | 10 +-
src/lib/log/log_formatter.cc | 7 +
src/lib/log/log_formatter.h | 31 +-
src/lib/log/logger.cc | 7 +
src/lib/log/logger.h | 23 +
src/lib/log/logger_impl.cc | 35 +-
src/lib/log/logger_impl.h | 18 +-
src/lib/log/logger_manager.cc | 8 +
src/lib/log/logger_unittest_support.cc | 3 +
src/lib/log/message_dictionary.cc | 6 +-
src/lib/log/message_exception.h | 9 +
src/lib/log/tests/.gitignore | 4 +
src/lib/log/tests/Makefile.am | 19 +
src/lib/log/tests/log_formatter_unittest.cc | 21 +-
.../lib/log/tests/log_test_messages.mes | 30 +-
src/lib/log/tests/logger_example.cc | 10 +-
src/lib/log/tests/logger_lock_test.cc | 64 +
src/lib/log/tests/logger_lock_test.sh.in | 46 +
src/lib/log/tests/logger_unittest.cc | 66 +
src/lib/log/tests/run_initializer_unittests.cc | 1 +
src/lib/log/tests/run_unittests.cc | 1 +
src/lib/python/isc/bind10/tests/Makefile.am | 1 +
src/lib/python/isc/config/cfgmgr.py | 2 +-
src/lib/python/isc/config/cfgmgr_messages.mes | 9 +-
src/lib/python/isc/config/tests/Makefile.am | 1 +
src/lib/python/isc/ddns/libddns_messages.mes | 50 +-
src/lib/python/isc/ddns/logger.py | 17 +-
src/lib/python/isc/ddns/session.py | 328 +-
src/lib/python/isc/ddns/tests/Makefile.am | 2 +-
src/lib/python/isc/ddns/tests/session_tests.py | 727 ++--
src/lib/python/isc/ddns/tests/zone_config_tests.py | 57 +-
src/lib/python/isc/ddns/zone_config.py | 39 +-
src/lib/python/isc/log/log.cc | 10 +-
src/lib/python/isc/log/tests/Makefile.am | 3 +
src/lib/python/isc/server_common/tests/Makefile.am | 1 +
src/lib/python/isc/xfrin/diff.py | 39 +-
src/lib/python/isc/xfrin/tests/Makefile.am | 1 +
src/lib/python/isc/xfrin/tests/diff_tests.py | 89 +-
src/lib/testutils/socket_request.h | 2 +-
src/lib/util/Makefile.am | 4 +
src/lib/util/buffer.h | 4 +-
src/lib/util/interprocess_sync.h | 149 +
src/lib/util/interprocess_sync_file.cc | 130 +
src/lib/util/interprocess_sync_file.h | 91 +
.../resource.cc => interprocess_sync_null.cc} | 35 +-
src/lib/util/interprocess_sync_null.h | 64 +
src/lib/util/locks.h | 2 +-
src/lib/util/tests/Makefile.am | 2 +
.../util/tests/interprocess_sync_file_unittest.cc | 174 +
.../util/tests/interprocess_sync_null_unittest.cc | 76 +
src/lib/util/tests/run_unittests.cc | 2 +
src/lib/xfr/xfrout_client.cc | 10 +-
tests/lettuce/features/bindctl_commands.feature | 2 +-
tests/tools/perfdhcp/Makefile.am | 19 +-
tests/tools/perfdhcp/perfdhcp.cc | 3565 --------------------
127 files changed, 3739 insertions(+), 4288 deletions(-)
create mode 100644 src/bin/dhcp4/tests/dhcp4_test.py
copy tests/system/cleanall.sh => src/lib/log/tests/log_test_messages.mes (55%)
mode change 100755 => 100644
create mode 100644 src/lib/log/tests/logger_lock_test.cc
create mode 100755 src/lib/log/tests/logger_lock_test.sh.in
create mode 100644 src/lib/util/interprocess_sync.h
create mode 100644 src/lib/util/interprocess_sync_file.cc
create mode 100644 src/lib/util/interprocess_sync_file.h
copy src/lib/util/{unittests/resource.cc => interprocess_sync_null.cc} (69%)
create mode 100644 src/lib/util/interprocess_sync_null.h
create mode 100644 src/lib/util/tests/interprocess_sync_file_unittest.cc
create mode 100644 src/lib/util/tests/interprocess_sync_null_unittest.cc
delete mode 100644 tests/tools/perfdhcp/perfdhcp.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 5b9c9d1..6133ea6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+444. [bug] jinmei
+ libdatasrc: fixed ZoneFinder for database-based data sources so
+ that it handles type DS query correctly, i.e., treating it as
+ authoritative data even on a delegation point.
+ (Trac #1912, git 7130da883f823ce837c10cbf6e216a15e1996e5d)
+
+443. [func]* muks
+ The logger now uses a lockfile named `logger_lockfile' that is
+ created in the local state directory to mutually separate
+ individual logging operations from various processes. This is
+ done so that log messages from different processes don't mix
+ together in the middle of lines. The `logger_lockfile` is created
+ with file permission mode 0660. BIND 10's local state directory
+ should be writable and perhaps have g+s mode bit so that the
+ `logger_lockfile` can be opened by a group of processes.
+ (Trac #1704, git ad8d445dd0ba208107eb239405166c5c2070bd8b)
+
+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.
diff --git a/Makefile.am b/Makefile.am
index 54216b6..ccc5524 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -405,3 +405,5 @@ EXTRA_DIST += ext/coroutine/coroutine.h
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = dns++.pc
+
+CLEANFILES = $(abs_top_builddir)/logger_lockfile
diff --git a/configure.ac b/configure.ac
index 6935431..7b8071e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1187,6 +1187,7 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
src/lib/log/tests/local_file_test.sh
+ src/lib/log/tests/logger_lock_test.sh
src/lib/log/tests/severity_test.sh
src/lib/log/tests/tempdir.h
src/lib/util/python/mkpywrapper.py
@@ -1235,6 +1236,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/log/tests/destination_test.sh
chmod +x src/lib/log/tests/init_logger_test.sh
chmod +x src/lib/log/tests/local_file_test.sh
+ chmod +x src/lib/log/tests/logger_lock_test.sh
chmod +x src/lib/log/tests/severity_test.sh
chmod +x src/lib/util/python/mkpywrapper.py
chmod +x src/lib/util/python/gen_wiredata.py
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 2bf43ff..2a47c38 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -390,8 +390,9 @@ private:
AuthSrv* server_;
};
-AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client,
- BaseSocketSessionForwarder& ddns_forwarder)
+AuthSrv::AuthSrv(const bool use_cache,
+ isc::xfr::AbstractXfroutClient& xfrout_client,
+ isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
{
impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
checkin_ = new ConfigChecker(this);
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index 8d6e5ed..9e085e3 100644
--- a/src/bin/bind10/bind10.8
+++ b/src/bin/bind10/bind10.8
@@ -42,7 +42,7 @@ b10\-config\&.db\&.
.RS 4
This will create a backup of the existing configuration file, remove it and start
b10\-cfgmgr(8)
-with the default configuration\&. The name of the backup file can be found in the logs (\fICFGMGR_RENAMED_CONFIG_FILE\fR)\&. (It will append a number to the backup filename if a previous backup file exists\&.)
+with the default configuration\&. The name of the backup file can be found in the logs (\fICFGMGR_BACKED_UP_CONFIG_FILE\fR)\&. (It will append a number to the backup filename if a previous backup file exists\&.)
.RE
.PP
\fB\-\-cmdctl\-port\fR \fIport\fR
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/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 37b845d..08e16c6 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -64,6 +64,7 @@ import posix
import copy
from bind10_config import LIBEXECPATH
+import bind10_config
import isc.cc
import isc.util.process
import isc.net.parse
@@ -1122,6 +1123,28 @@ def unlink_pid_file(pid_file):
if error.errno is not errno.ENOENT:
raise
+def remove_lock_files():
+ """
+ Remove various lock files which were created by code such as in the
+ logger. This function should be called after BIND 10 shutdown.
+ """
+
+ lockfiles = ["logger_lockfile"]
+
+ lpath = bind10_config.DATA_PATH
+ if "B10_FROM_BUILD" in os.environ:
+ lpath = os.environ["B10_FROM_BUILD"]
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ lpath = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
+ if "B10_LOCKFILE_DIR_FROM_BUILD" in os.environ:
+ lpath = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"]
+
+ for f in lockfiles:
+ fname = lpath + '/' + f
+ if os.path.isfile(fname):
+ os.unlink(fname)
+
+ return
def main():
global options
@@ -1201,6 +1224,7 @@ def main():
finally:
# Clean up the filesystem
unlink_pid_file(options.pid_file)
+ remove_lock_files()
if boss_of_bind is not None:
boss_of_bind.remove_socket_srv()
sys.exit(boss_of_bind.exitcode)
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index d54ee56..a5e3fab 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -23,6 +23,7 @@ endif
chmod +x $(abs_builddir)/$$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 84a9da9..3b80fb5 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -1463,6 +1463,41 @@ class SocketSrvTest(unittest.TestCase):
self.assertEqual({}, self.__boss._unix_sockets)
self.assertTrue(sock.closed)
+class TestFunctions(unittest.TestCase):
+ def setUp(self):
+ self.lockfile_testpath = \
+ "@abs_top_builddir@/src/bin/bind10/tests/lockfile_test"
+ self.assertFalse(os.path.exists(self.lockfile_testpath))
+ os.mkdir(self.lockfile_testpath)
+ self.assertTrue(os.path.isdir(self.lockfile_testpath))
+
+ def tearDown(self):
+ os.rmdir(self.lockfile_testpath)
+ self.assertFalse(os.path.isdir(self.lockfile_testpath))
+ os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+
+ def test_remove_lock_files(self):
+ os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
+
+ # create lockfiles for the testcase
+ lockfiles = ["logger_lockfile"]
+ for f in lockfiles:
+ fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+ self.assertFalse(os.path.exists(fname))
+ open(fname, "w").close()
+ self.assertTrue(os.path.isfile(fname))
+
+ # first call should clear up all the lockfiles
+ bind10_src.remove_lock_files()
+
+ # check if the lockfiles exist
+ for f in lockfiles:
+ fname = os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] + '/' + f
+ self.assertFalse(os.path.isfile(fname))
+
+ # second call should not assert anyway
+ bind10_src.remove_lock_files()
+
if __name__ == '__main__':
# store os.environ for test_unchanged_environment
original_os_environ = copy.deepcopy(os.environ)
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.8 b/src/bin/cfgmgr/b10-cfgmgr.8
index 0524d9b..83f7b7d 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.8
+++ b/src/bin/cfgmgr/b10-cfgmgr.8
@@ -54,7 +54,7 @@ The arguments are as follows:
.RS 4
This will create a backup of the existing configuration file, remove it, and
b10\-cfgmgr(8)
-will use the default configurations\&. The name of the backup file can be found in the logs (\fICFGMGR_RENAMED_CONFIG_FILE\fR)\&. (It will append a number to the backup filename if a previous backup file exists\&.)
+will use the default configurations\&. The name of the backup file can be found in the logs (\fICFGMGR_BACKED_UP_CONFIG_FILE\fR)\&. (It will append a number to the backup filename if a previous backup file exists\&.)
.RE
.PP
\fB\-c\fR \fIconfig\-filename\fR, \fB\-\-config\-filename\fR \fIconfig\-filename\fR
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/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index 89d89ea..b5b65f6 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -22,5 +22,6 @@ endif
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
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/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
index c03b262..b4231b3 100644
--- a/src/bin/dbutil/tests/Makefile.am
+++ b/src/bin/dbutil/tests/Makefile.am
@@ -3,4 +3,5 @@ SUBDIRS = . testdata
# Tests of the update script.
check-local:
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(SHELL) $(abs_builddir)/dbutil_test.sh
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index de69100..9c432f4 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -18,12 +18,19 @@
import sys; sys.path.append ('@@PYTHONPATH@@')
import isc
+from isc.acl.dns import REQUEST_LOADER
import bind10_config
from isc.dns import *
+import isc.ddns.session
+from isc.ddns.zone_config import ZoneConfig
+from isc.ddns.logger import ClientFormatter, ZoneFormatter
from isc.config.ccsession import *
-from isc.cc import SessionError, SessionTimeout
+from isc.cc import SessionError, SessionTimeout, ProtocolError
import isc.util.process
import isc.util.cio.socketsession
+from isc.notify.notify_out import ZONE_NEW_DATA_READY_CMD
+import isc.server_common.tsig_keyring
+from isc.datasrc import DataSourceClient
import select
import errno
@@ -39,29 +46,43 @@ isc.log.init("b10-ddns")
logger = isc.log.Logger("ddns")
TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+# Well known path settings. We need to define
+# SPECFILE_LOCATION: ddns configuration spec file
+# SOCKET_FILE: Unix domain socket file to communicate with b10-auth
+# AUTH_SPECFILE_LOCATION: b10-auth configuration spec file (tentatively
+# necessarily for sqlite3-only-and-older-datasrc-API stuff). This should be
+# gone once we migrate to the new API and start using generalized config.
+#
# If B10_FROM_SOURCE is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
# installed on the system
if "B10_FROM_SOURCE" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
- "src" + os.sep + "bin" + os.sep + "ddns" + os.sep + "ddns.spec"
+ SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/ddns"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "ddns.spec"
- SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR)\
- .replace("${prefix}", PREFIX)
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR)
+ SPECFILE_PATH = SPECFILE_PATH.replace("${prefix}", PREFIX)
-SOCKET_FILE = bind10_config.DATA_PATH + '/ddns_socket'
if "B10_FROM_BUILD" in os.environ:
+ AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
- SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
- "/ddns_socket"
+ SOCKET_FILE_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"]
else:
- SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/ddns_socket"
+ SOCKET_FILE_PATH = os.environ["B10_FROM_BUILD"]
+else:
+ SOCKET_FILE_PATH = bind10_config.DATA_PATH
+ AUTH_SPECFILE_PATH = SPECFILE_PATH
+
+SPECFILE_LOCATION = SPECFILE_PATH + "/ddns.spec"
+SOCKET_FILE = SOCKET_FILE_PATH + '/ddns_socket'
+AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + '/auth.spec'
isc.util.process.rename()
+# Cooperating modules
+XFROUT_MODULE_NAME = 'Xfrout'
+
class DDNSConfigError(Exception):
'''An exception indicating an error in updating ddns configuration.
@@ -93,6 +114,42 @@ def clear_socket():
if os.path.exists(SOCKET_FILE):
os.remove(SOCKET_FILE)
+def get_datasrc_client(cc_session):
+ '''Return data source client for update requests.
+
+ This is supposed to have a very short lifetime and should soon be replaced
+ with generic data source configuration framework. Based on that
+ observation we simply hardcode everything except the SQLite3 database file,
+ which will be retrieved from the auth server configuration (this behavior
+ will also be deprecated). When something goes wrong with it this function
+ still returns a dummy client so that the caller doesn't have to bother
+ to handle the error (which would also have to be replaced anyway).
+ The caller will subsequently call its find_zone method via an update
+ session object, which will result in an exception, and then result in
+ a SERVFAIL response.
+
+ Once we are ready for introducing the general framework, the whole
+ function will simply be removed.
+
+ '''
+ HARDCODED_DATASRC_CLASS = RRClass.IN()
+ file, is_default = cc_session.get_remote_config_value("Auth",
+ "database_file")
+ # See xfrout.py:get_db_file() for this trick:
+ if is_default and "B10_FROM_BUILD" in os.environ:
+ file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
+ datasrc_config = '{ "database_file": "' + file + '"}'
+ try:
+ return HARDCODED_DATASRC_CLASS, DataSourceClient('sqlite3',
+ datasrc_config)
+ except isc.datasrc.Error as ex:
+ class DummyDataSourceClient:
+ def __init__(self, ex):
+ self.__ex = ex
+ def find_zone(self, zone_name):
+ raise isc.datasrc.Error(self.__ex)
+ return HARDCODED_DATASRC_CLASS, DummyDataSourceClient(ex)
+
class DDNSServer:
def __init__(self, cc_session=None):
'''
@@ -110,8 +167,17 @@ class DDNSServer:
self.config_handler,
self.command_handler)
+ # Initialize configuration with defaults. Right now 'zones' is the
+ # only configuration, so we simply directly set it here.
self._config_data = self._cc.get_full_config()
+ self._zone_config = self.__update_zone_config(
+ self._cc.get_default_value('zones'))
self._cc.start()
+
+ # Get necessary configurations from remote modules.
+ self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
+ isc.server_common.tsig_keyring.init_keyring(self._cc)
+
self._shutdown = False
# List of the session receivers where we get the requests
self._socksession_receivers = {}
@@ -120,12 +186,52 @@ class DDNSServer:
self._listen_socket.bind(SOCKET_FILE)
self._listen_socket.listen(16)
+ # Create reusable resources
+ self.__request_msg = Message(Message.PARSE)
+ self.__response_renderer = MessageRenderer()
+
+ # The following attribute(s) are essentially private and constant,
+ # but defined as "protected" so that test code can customize them.
+ # They should not be overridden for any other purposes.
+ #
+ # DDNS Protocol handling class.
+ self._UpdateSessionClass = isc.ddns.session.UpdateSession
+
+ class InternalError(Exception):
+ '''Exception for internal errors in an update session.
+
+ This exception is expected to be caught within the server class,
+ only used for controling the code flow.
+
+ '''
+ pass
+
def config_handler(self, new_config):
'''Update config data.'''
- # TODO: Handle exceptions and turn them to an error response
- # (once we have any configuration)
- answer = create_answer(0)
- return answer
+ try:
+ if 'zones' in new_config:
+ self._zone_config = \
+ self.__update_zone_config(new_config['zones'])
+ return create_answer(0)
+ except Exception as ex:
+ # We catch any exception here. That includes any syntax error
+ # against the configuration spec. The config interface is too
+ # complicated and it's not clear how much validation is performed
+ # there, so, while assuming it's unlikely to happen, we act
+ # proactively.
+ logger.error(DDNS_CONFIG_HANDLER_ERROR, ex)
+ return create_answer(1, "Failed to handle new configuration: " +
+ str(ex))
+
+ def __update_zone_config(self, new_zones_config):
+ '''Handle zones configuration update.'''
+ new_zones = {}
+ for zone_config in new_zones_config:
+ origin = Name(zone_config['origin'])
+ rrclass = RRClass(zone_config['class'])
+ update_acl = zone_config['update_acl']
+ new_zones[(origin, rrclass)] = REQUEST_LOADER.load(update_acl)
+ return new_zones
def command_handler(self, cmd, args):
'''
@@ -168,10 +274,10 @@ class DDNSServer:
Accept another connection and create the session receiver.
"""
try:
- sock = self._listen_socket.accept()
+ (sock, remote_addr) = self._listen_socket.accept()
fileno = sock.fileno()
logger.debug(TRACE_BASIC, DDNS_NEW_CONN, fileno,
- sock.getpeername())
+ remote_addr if remote_addr else '<anonymous address>')
receiver = isc.util.cio.socketsession.SocketSessionReceiver(sock)
self._socksession_receivers[fileno] = (sock, receiver)
except (socket.error, isc.util.cio.socketsession.SocketSessionError) \
@@ -180,7 +286,30 @@ class DDNSServer:
# continue with the rest
logger.error(DDNS_ACCEPT_FAILURE, e)
- def handle_request(self, request):
+ def __check_request_tsig(self, msg, req_data):
+ '''TSIG checker for update requests.
+
+ This is a helper method for handle_request() below. It examines
+ the given update request message to see if it contains a TSIG RR,
+ and verifies the signature if it does. It returs the TSIG context
+ used for the verification, or None if the request doesn't contain
+ a TSIG. If the verification fails it simply raises an exception
+ as handle_request() assumes it should succeed.
+
+ '''
+ tsig_record = msg.get_tsig_record()
+ if tsig_record is None:
+ return None
+ tsig_ctx = TSIGContext(tsig_record.get_name(),
+ tsig_record.get_rdata().get_algorithm(),
+ isc.server_common.tsig_keyring.get_keyring())
+ tsig_error = tsig_ctx.verify(tsig_record, req_data)
+ if tsig_error != TSIGError.NOERROR:
+ raise self.InternalError("Failed to verify request's TSIG: " +
+ str(tsig_error))
+ return tsig_ctx
+
+ def handle_request(self, req_session):
"""
This is the place where the actual DDNS processing is done. Other
methods are either subroutines of this method or methods doing the
@@ -190,12 +319,131 @@ class DDNSServer:
It is called with the request being session as received from
SocketSessionReceiver, i.e. tuple
(socket, local_address, remote_address, data).
+
+ In general, this method doesn't propagate exceptions outside the
+ method. Most of protocol or system errors will result in an error
+ response to the update client or dropping the update request.
+ The update session class should also ensure this. Critical exceptions
+ such as memory allocation failure will be propagated, however, and
+ will subsequently terminate the server process.
+
+ Return: True if a response to the request is successfully sent;
+ False otherwise. The return value wouldn't be useful for the server
+ itself; it's provided mainly for testing purposes.
+
"""
- # TODO: Implement the magic
+ # give tuple elements intuitive names
+ (sock, local_addr, remote_addr, req_data) = req_session
+
+ # The session sender (b10-auth) should have made sure that this is
+ # a validly formed DNS message of OPCODE being UPDATE, and if it's
+ # TSIG signed, its key is known to the system and the signature is
+ # valid. Messages that don't meet these should have been resopnded
+ # or dropped by the sender, so if such error is detected we treat it
+ # as an internal error and don't bother to respond.
+ try:
+ if sock.proto == socket.IPPROTO_TCP:
+ raise self.InternalError('TCP requests are not yet supported')
+ self.__request_msg.clear(Message.PARSE)
+ # specify PRESERVE_ORDER as we need to handle each RR separately.
+ self.__request_msg.from_wire(req_data, Message.PRESERVE_ORDER)
+ if self.__request_msg.get_opcode() != Opcode.UPDATE():
+ raise self.InternalError('Update request has unexpected '
+ 'opcode: ' +
+ str(self.__request_msg.get_opcode()))
+ tsig_ctx = self.__check_request_tsig(self.__request_msg, req_data)
+ except Exception as ex:
+ logger.error(DDNS_REQUEST_PARSE_FAIL, ex)
+ return False
+
+ # Let an update session object handle the request. Note: things around
+ # ZoneConfig will soon be substantially revised. For now we don't
+ # bother to generalize it.
+ datasrc_class, datasrc_client = get_datasrc_client(self._cc)
+ zone_cfg = ZoneConfig([], datasrc_class, datasrc_client,
+ self._zone_config)
+ update_session = self._UpdateSessionClass(self.__request_msg,
+ remote_addr, zone_cfg)
+ result, zname, zclass = update_session.handle()
+
+ # If the request should be dropped, we're done; otherwise, send the
+ # response generated by the session object.
+ if result == isc.ddns.session.UPDATE_DROP:
+ return False
+ msg = update_session.get_message()
+ self.__response_renderer.clear()
+ if tsig_ctx is not None:
+ msg.to_wire(self.__response_renderer, tsig_ctx)
+ else:
+ msg.to_wire(self.__response_renderer)
- # TODO: Don't propagate most of the exceptions (like datasrc errors),
- # just drop the packet.
- pass
+ ret = self.__send_response(sock, self.__response_renderer.get_data(),
+ remote_addr)
+ if result == isc.ddns.session.UPDATE_SUCCESS:
+ self.__notify_update(zname, zclass)
+ return ret
+
+ def __send_response(self, sock, data, dest):
+ '''Send DDNS response to the client.
+
+ Right now, this is a straightforward subroutine of handle_request(),
+ but is intended to be extended evetually so that it can handle more
+ comlicated operations for TCP (which requires asynchronous write).
+ Further, when we support multiple requests over a single TCP
+ connection, this method may even be shared by multiple methods.
+
+ Parameters:
+ sock: (python socket) the socket to which the response should be sent.
+ data: (binary) the response data
+ dest: (python socket address) the destion address to which the response
+ should be sent.
+
+ Return: True if the send operation succeds; otherwise False.
+
+ '''
+ try:
+ sock.sendto(data, dest)
+ except socket.error as ex:
+ logger.error(DDNS_RESPONSE_SOCKET_ERROR, ClientFormatter(dest), ex)
+ return False
+
+ return True
+
+ def __notify_update(self, zname, zclass):
+ '''Notify other modules of the update.
+
+ Note that we use blocking communication here. While the internal
+ communication bus is generally expected to be pretty responsive and
+ error free, notable delay can still occur, and in worse cases timeouts
+ or connection reset can happen. In these cases, even if the trouble
+ is temporary, the update service will be suspended for a while.
+ For a longer term we'll need to switch to asynchronous communication,
+ but for now we rely on the blocking operation.
+
+ Note also that we directly refer to the "protected" member of
+ ccsession (_cc._session) rather than creating a separate channel.
+ It's probably not the best practice, but hopefully we can introduce
+ a cleaner way when we support asynchronous communication.
+ At the moment we prefer the brevity with the use of internal channel
+ of the cc session.
+
+ '''
+ param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
+ msg = create_command(ZONE_NEW_DATA_READY_CMD, param)
+ modname = XFROUT_MODULE_NAME
+ try:
+ seq = self._cc._session.group_sendmsg(msg, modname)
+ answer, _ = self._cc._session.group_recvmsg(False, seq)
+ rcode, error_msg = parse_answer(answer)
+ except (SessionTimeout, SessionError, ProtocolError) as ex:
+ rcode = 1
+ error_msg = str(ex)
+ if rcode == 0:
+ logger.debug(TRACE_BASIC, DDNS_UPDATE_NOTIFY, modname,
+ ZoneFormatter(zname, zclass))
+ else:
+ logger.error(DDNS_UPDATE_NOTIFY_FAIL, modname,
+ ZoneFormatter(zname, zclass), error_msg)
def handle_session(self, fileno):
"""
diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec
index 55dab5c..70611e6 100644
--- a/src/bin/ddns/ddns.spec
+++ b/src/bin/ddns/ddns.spec
@@ -4,22 +4,36 @@
"config_data": [
{
"item_name": "zones",
- "item_type": "named_set",
+ "item_type": "list",
"item_optional": false,
- "item_default": {},
- "named_set_item_spec": {
+ "item_default": [],
+ "list_item_spec": {
"item_name": "entry",
"item_type": "map",
"item_optional": true,
"item_default": {
- "update_acl": [{"action": "ACCEPT", "from": "127.0.0.1"},
- {"action": "ACCEPT", "from": "::1"}]
+ "origin": "",
+ "class": "IN",
+ "update_acl": []
},
"map_item_spec": [
{
+ "item_name": "origin",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ {
"item_name": "update_acl",
"item_type": "list",
"item_optional": false,
+ "item_default": [],
"list_item_spec": {
"item_name": "acl_element",
"item_type": "any",
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 996e663..a30eb3c 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -38,6 +38,14 @@ configuration manager b10-cfgmgr is not running.
The ddns process encountered an error when installing the configuration at
startup time. Details of the error are included in the log message.
+% DDNS_CONFIG_HANDLER_ERROR failed to update ddns configuration: %1
+An update to b10-ddns configuration was delivered but an error was
+found while applying them. None of the delivered updates were applied
+to the running b10-ddns system, and the server will keep running with
+the existing configuration. If this happened in the initial
+configuration setup, the server will be running with the default
+configurations.
+
% DDNS_DROP_CONN dropping connection on file descriptor %1 because of error %2
There was an error on a connection with the b10-auth server (or whatever
connects to the ddns daemon). This might be OK, for example when the
@@ -62,6 +70,22 @@ coming from a b10-auth process.
The ddns process received a shutdown command from the command channel
and will now shut down.
+% DDNS_REQUEST_PARSE_FAIL failed to parse update request: %1
+b10-ddns received an update request via b10-auth, but the received
+data failed to pass minimum validation: it was either broken wire
+format data for a valid DNS message (e.g. it's shorter than the
+fixed-length header), or the opcode is not update, or TSIG is included
+in the request but it fails to validate. Since b10-auth should have
+performed this level of checks, such an error shouldn't be detected at
+this stage and should rather be considered an internal bug. This
+event is therefore logged at the error level, and the request is
+simply dropped. Additional information of the error is also logged.
+
+% DDNS_RESPONSE_SOCKET_ERROR failed to send update response to %1: %2
+Network I/O error happens in sending an update request. The
+client's address that caused the error and error details are also
+logged.
+
% DDNS_RUNNING ddns server is running and listening for updates
The ddns process has successfully started and is now ready to receive commands
and updates.
@@ -88,3 +112,26 @@ process will now shut down.
The b10-ddns process encountered an uncaught exception and will now shut
down. This is indicative of a programming error and should not happen under
normal circumstances. The exception type and message are printed.
+
+% DDNS_UPDATE_NOTIFY notified %1 of updates to %2
+Debug message. b10-ddns has made updates to a zone based on an update
+request and has successfully notified an external module of the updates.
+The notified module will use that information for updating its own
+state or any necessary protocol action such as zone reloading or sending
+notify messages to secondary servers.
+
+% DDNS_UPDATE_NOTIFY_FAIL failed to notify %1 of updates to %2: %3
+b10-ddns has made updates to a zone based on an update request and
+tried to notify an external module of the updates, but the
+notification fails. Severity of this effect depends on the type of
+the module. If it's b10-xfrout, this means DNS notify messages won't
+be sent to secondary servers of the zone. It's suboptimal, but not
+necessarily critical as the secondary servers will try to check the
+zone's status periodically. If it's b10-auth and the notification was
+needed to have it reload the corresponding zone, it's more serious
+because b10-auth won't be able to serve the new version of the zone
+unless some explicit recovery action is taken. So the administrator
+needs to examine this message and takes an appropriate action. In
+either case, this notification is generally expected to succeed; so
+the fact it fails itself means there's something wrong in the BIND 10
+system, and it would be advisable to check other log messages.
diff --git a/src/bin/ddns/tests/Makefile.am b/src/bin/ddns/tests/Makefile.am
index cd1082f..5c824d4 100644
--- a/src/bin/ddns/tests/Makefile.am
+++ b/src/bin/ddns/tests/Makefile.am
@@ -25,5 +25,6 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/ddns:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 395aacc..992e587 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -15,28 +15,73 @@
'''Tests for the DDNS module'''
-import unittest
-import isc
+from isc.ddns.session import *
+from isc.dns import *
+from isc.acl.acl import ACCEPT
+import isc.util.cio.socketsession
+from isc.cc.session import SessionTimeout, SessionError, ProtocolError
+from isc.datasrc import DataSourceClient
+from isc.config.ccsession import create_answer
import ddns
-import isc.config
-import select
import errno
-import isc.util.cio.socketsession
+import os
+import select
+import shutil
import socket
-import os.path
+import unittest
+
+# Some common test parameters
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
+TEST_ZONE_NAME = Name('example.org')
+TEST_ZONE_NAME_STR = TEST_ZONE_NAME.to_text()
+UPDATE_RRTYPE = RRType.SOA()
+TEST_QID = 5353 # arbitrary chosen
+TEST_RRCLASS = RRClass.IN()
+TEST_RRCLASS_STR = TEST_RRCLASS.to_text()
+TEST_SERVER6 = ('2001:db8::53', 53, 0, 0)
+TEST_CLIENT6 = ('2001:db8::1', 53000, 0, 0)
+TEST_SERVER4 = ('192.0.2.53', 53)
+TEST_CLIENT4 = ('192.0.2.1', 53534)
+TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE)
+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])
+# TSIG key for tests when needed. The key name is TEST_ZONE_NAME.
+TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==")
+# TSIG keyring that contanins the test key
+TEST_TSIG_KEYRING = TSIGKeyRing()
+TEST_TSIG_KEYRING.add(TEST_TSIG_KEY)
+# Another TSIG key not in the keyring, making verification fail
+BAD_TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
class FakeSocket:
"""
A fake socket. It only provides a file number, peer name and accept method.
"""
def __init__(self, fileno):
+ self.proto = socket.IPPROTO_UDP
self.__fileno = fileno
+ self._sent_data = None
+ self._sent_addr = None
+ # customizable by tests; if set to True, sendto() will throw after
+ # recording the parameters.
+ self._raise_on_send = False
def fileno(self):
return self.__fileno
def getpeername(self):
return "fake_unix_socket"
def accept(self):
- return FakeSocket(self.__fileno + 1)
+ return FakeSocket(self.__fileno + 1), '/dummy/path'
+ def sendto(self, data, addr):
+ self._sent_data = data
+ self._sent_addr = addr
+ if self._raise_on_send:
+ raise socket.error('test socket failure')
+ def clear(self):
+ '''Clear internal instrumental data.'''
+ self._sent_data = None
+ self._sent_addr = None
class FakeSessionReceiver:
"""
@@ -51,14 +96,79 @@ class FakeSessionReceiver:
"""
return self._socket
+class FakeUpdateSession:
+ '''A fake update session, emulating isc.ddns.session.UpdateSession.
+
+ It provides the same interfaces as UpdateSession with skipping complicated
+ internal protocol processing and returning given faked results. This
+ will help simplify test setups.
+
+ '''
+ def __init__(self, msg, client_addr, zone_config, faked_result):
+ '''Faked constructor.
+
+ It takes an additional faked_result parameter. It will be used
+ as the result value of handle(). If its value is UPDATE_ERROR,
+ get_message() will create a response message whose Rcode is
+ REFUSED.
+
+ '''
+ self.__msg = msg
+ self.__faked_result = faked_result
+
+ def handle(self):
+ if self.__faked_result == UPDATE_SUCCESS:
+ return self.__faked_result, TEST_ZONE_NAME, TEST_RRCLASS
+ return self.__faked_result, None, None
+
+ def get_message(self):
+ self.__msg.make_response()
+ self.__msg.clear_section(SECTION_ZONE)
+ if self.__faked_result == UPDATE_SUCCESS:
+ self.__msg.set_rcode(Rcode.NOERROR())
+ else:
+ self.__msg.set_rcode(Rcode.REFUSED())
+ return self.__msg
+
+class FakeKeyringModule:
+ '''Fake the entire isc.server_common.tsig_keyring module.'''
+
+ def init_keyring(self, cc):
+ '''Set the instrumental attribute to True when called.
+
+ It can be used for a test that confirms TSIG key initialization is
+ surely performed. This class doesn't use any CC session, so the
+ cc parameter will be ignored.
+
+ '''
+ self.initialized = True
+
+ def get_keyring(self):
+ '''Simply return the predefined TSIG keyring unconditionally.'''
+ return TEST_TSIG_KEYRING
+
class MyCCSession(isc.config.ConfigData):
- '''Fake session with minimal interface compliance'''
+ '''Fake session with minimal interface compliance.'''
+
+ # faked CC sequence used in group_send/recvmsg
+ FAKE_SEQUENCE = 53
+
def __init__(self):
module_spec = isc.config.module_spec_from_file(
ddns.SPECFILE_LOCATION)
isc.config.ConfigData.__init__(self, module_spec)
self._started = False
self._stopped = False
+ # Used as the return value of get_remote_config_value. Customizable.
+ self.auth_db_file = READ_ZONE_DB_FILE
+ # faked cc channel, providing group_send/recvmsg itself. The following
+ # attributes are for inspection/customization in tests.
+ self._session = self
+ self._sent_msg = []
+ self._recvmsg_called = 0
+ self._answer_code = 0 # code used in answer returned via recvmsg
+ self._sendmsg_exception = None # will be raised from sendmsg if !None
+ self._recvmsg_exception = None # will be raised from recvmsg if !None
def start(self):
'''Called by DDNSServer initialization, but not used in tests'''
@@ -74,6 +184,39 @@ class MyCCSession(isc.config.ConfigData):
"""
return FakeSocket(1)
+ def add_remote_config(self, spec_file_name):
+ pass
+
+ def get_remote_config_value(self, module_name, item):
+ if module_name == "Auth" and item == "database_file":
+ return self.auth_db_file, False
+
+ def group_sendmsg(self, msg, group):
+ # remember the passed parameter, and return dummy sequence
+ self._sent_msg.append((msg, group))
+ if self._sendmsg_exception is not None:
+ raise self._sendmsg_exception
+ return self.FAKE_SEQUENCE
+
+ def group_recvmsg(self, nonblock, seq):
+ self._recvmsg_called += 1
+ if seq != self.FAKE_SEQUENCE:
+ raise RuntimeError('unexpected CC sequence: ' + str(seq))
+ if self._recvmsg_exception is not None:
+ raise self._recvmsg_exception
+ if self._answer_code is 0:
+ return create_answer(0), None
+ else:
+ return create_answer(self._answer_code, "dummy error value"), None
+
+ def clear_msg(self):
+ '''Clear instrumental attributes related session messages.'''
+ self._sent_msg = []
+ self._recvmsg_called = 0
+ self._answer_code = 0
+ self._sendmsg_exception = None
+ self._recvmsg_exception = None
+
class MyDDNSServer():
'''Fake DDNS server used to test the main() function'''
def __init__(self):
@@ -104,6 +247,8 @@ class TestDDNSServer(unittest.TestCase):
def setUp(self):
cc_session = MyCCSession()
self.assertFalse(cc_session._started)
+ self.orig_tsig_keyring = isc.server_common.tsig_keyring
+ isc.server_common.tsig_keyring = FakeKeyringModule()
self.ddns_server = ddns.DDNSServer(cc_session)
self.__cc_session = cc_session
self.assertTrue(cc_session._started)
@@ -118,6 +263,7 @@ class TestDDNSServer(unittest.TestCase):
ddns.select.select = select.select
ddns.isc.util.cio.socketsession.SocketSessionReceiver = \
isc.util.cio.socketsession.SocketSessionReceiver
+ isc.server_common.tsig_keyring = self.orig_tsig_keyring
def test_listen(self):
'''
@@ -141,12 +287,92 @@ class TestDDNSServer(unittest.TestCase):
ddns.clear_socket()
self.assertFalse(os.path.exists(ddns.SOCKET_FILE))
+ def test_initial_config(self):
+ # right now, the only configuration is the zone configuration, whose
+ # default should be an empty map.
+ self.assertEqual({}, self.ddns_server._zone_config)
+
def test_config_handler(self):
- # Config handler does not do anything yet, but should at least
- # return 'ok' for now.
- new_config = {}
+ # Update with a simple zone configuration: including an accept-all ACL
+ new_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] } ] }
+ answer = self.ddns_server.config_handler(new_config)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+ acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)]
+ self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
+
+ # Slightly more complicated one: containing multiple ACLs
+ new_config = { 'zones': [ { 'origin': 'example.com',
+ 'class': 'CH',
+ 'update_acl': [{'action': 'REJECT',
+ 'from': '2001:db8::1'}] },
+ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] },
+ { 'origin': 'example.org',
+ 'class': 'CH',
+ 'update_acl': [{'action': 'DROP'}] } ] }
answer = self.ddns_server.config_handler(new_config)
self.assertEqual((0, None), isc.config.parse_answer(answer))
+ self.assertEqual(3, len(self.ddns_server._zone_config))
+ acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)]
+ self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
+
+ # empty zone config
+ new_config = { 'zones': [] }
+ answer = self.ddns_server.config_handler(new_config)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+ self.assertEqual({}, self.ddns_server._zone_config)
+
+ # bad zone config data: bad name. The previous config shouls be kept.
+ bad_config = { 'zones': [ { 'origin': 'bad..example',
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] } ] }
+ answer = self.ddns_server.config_handler(bad_config)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+ self.assertEqual({}, self.ddns_server._zone_config)
+
+ # bad zone config data: bad class.
+ bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': 'badclass',
+ 'update_acl': [{'action': 'ACCEPT'}] } ] }
+ answer = self.ddns_server.config_handler(bad_config)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+ self.assertEqual({}, self.ddns_server._zone_config)
+
+ # bad zone config data: bad ACL.
+ bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'badaction'}]}]}
+ answer = self.ddns_server.config_handler(bad_config)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+ self.assertEqual({}, self.ddns_server._zone_config)
+
+ # the first zone cofig is valid, but not the second. the first one
+ # shouldn't be installed.
+ bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] },
+ { 'origin': 'bad..example',
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] } ] }
+ answer = self.ddns_server.config_handler(bad_config)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+ self.assertEqual({}, self.ddns_server._zone_config)
+
+ # Half-broken case: 'origin, class' pair is duplicate. For now we
+ # we accept it (the latter one will win)
+ dup_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'REJECT'}] },
+ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'ACCEPT'}] } ] }
+ answer = self.ddns_server.config_handler(dup_config)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+ acl = self.ddns_server._zone_config[(TEST_ZONE_NAME, TEST_RRCLASS)]
+ self.assertEqual(ACCEPT, acl.execute(TEST_ACL_CONTEXT))
def test_shutdown_command(self):
'''Test whether the shutdown command works'''
@@ -361,6 +587,256 @@ class TestDDNSServer(unittest.TestCase):
self.__select_expected = ([1, 2], [], [], None)
self.assertRaises(select.error, self.ddns_server.run)
+def create_msg(opcode=Opcode.UPDATE(), zones=[TEST_ZONE_RECORD], prereq=[],
+ tsigctx=None):
+ msg = Message(Message.RENDER)
+ msg.set_qid(TEST_QID)
+ msg.set_opcode(opcode)
+ msg.set_rcode(Rcode.NOERROR())
+ for z in zones:
+ msg.add_question(z)
+ for p in prereq:
+ msg.add_rrset(SECTION_PREREQUISITE, p)
+
+ renderer = MessageRenderer()
+ if tsigctx is not None:
+ msg.to_wire(renderer, tsigctx)
+ 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 renderer.get_data()
+
+
+class TestDDNSSession(unittest.TestCase):
+ def setUp(self):
+ self.__cc_session = MyCCSession()
+ self.assertFalse(self.__cc_session._started)
+ self.orig_tsig_keyring = isc.server_common.tsig_keyring
+ isc.server_common.tsig_keyring = FakeKeyringModule()
+ self.server = ddns.DDNSServer(self.__cc_session)
+ self.server._UpdateSessionClass = self.__fake_session_creator
+ self.__faked_result = UPDATE_SUCCESS # will be returned by fake session
+ self.__sock = FakeSocket(-1)
+
+ def tearDown(self):
+ self.assertTrue(isc.server_common.tsig_keyring.initialized)
+ isc.server_common.tsig_keyring = self.orig_tsig_keyring
+
+ def __fake_session_creator(self, req_message, client_addr, zone_config):
+ # remember the passed message for possible inspection later.
+ self.__req_message = req_message
+ return FakeUpdateSession(req_message, client_addr, zone_config,
+ self.__faked_result)
+
+ def check_update_response(self, resp_wire, expected_rcode=Rcode.NOERROR(),
+ tsig_ctx=None):
+ '''Check if given wire data are valid form of update response.
+
+ In this implementation, zone/prerequisite/update sections should be
+ empty in responses.
+
+ If tsig_ctx (isc.dns.TSIGContext) is not None, the response should
+ be TSIG signed and the signature should be verifiable with the context
+ that has signed the corresponding request.
+
+ '''
+ msg = Message(Message.PARSE)
+ msg.from_wire(resp_wire)
+ if tsig_ctx is not None:
+ tsig_record = msg.get_tsig_record()
+ self.assertNotEqual(None, tsig_record)
+ self.assertEqual(TSIGError.NOERROR,
+ tsig_ctx.verify(tsig_record, resp_wire))
+ self.assertEqual(Opcode.UPDATE(), msg.get_opcode())
+ self.assertEqual(expected_rcode, msg.get_rcode())
+ self.assertEqual(TEST_QID, msg.get_qid())
+ for section in [SECTION_ZONE, SECTION_PREREQUISITE, SECTION_UPDATE]:
+ self.assertEqual(0, msg.get_rr_count(section))
+
+ def check_session(self, result=UPDATE_SUCCESS, ipv6=True, tsig_key=None):
+ # reset test parameters
+ self.__sock.clear()
+ self.__faked_result = result
+
+ server_addr = TEST_SERVER6 if ipv6 else TEST_SERVER4
+ client_addr = TEST_CLIENT6 if ipv6 else TEST_CLIENT4
+ tsig = TSIGContext(tsig_key) if tsig_key is not None else None
+ rcode = Rcode.NOERROR() if result == UPDATE_SUCCESS else Rcode.REFUSED()
+ has_response = (result != UPDATE_DROP)
+
+ self.assertEqual(has_response,
+ self.server.handle_request((self.__sock,
+ server_addr, client_addr,
+ create_msg(tsigctx=tsig))))
+ if has_response:
+ self.assertEqual(client_addr, self.__sock._sent_addr)
+ self.check_update_response(self.__sock._sent_data, rcode)
+ else:
+ self.assertEqual((None, None), (self.__sock._sent_addr,
+ self.__sock._sent_data))
+
+ def test_handle_request(self):
+ '''Basic request handling without any unexpected errors.'''
+ # Success, without TSIG
+ self.check_session()
+ # Update will be refused with a response.
+ self.check_session(UPDATE_ERROR, ipv6=False)
+ # Update will be refused and dropped
+ self.check_session(UPDATE_DROP)
+ # Success, with TSIG
+ self.check_session(ipv6=False, tsig_key=TEST_TSIG_KEY)
+ # Update will be refused with a response, with TSIG.
+ self.check_session(UPDATE_ERROR, tsig_key=TEST_TSIG_KEY)
+ # Update will be refused and dropped, with TSIG (doesn't matter though)
+ self.check_session(UPDATE_DROP, ipv6=False, tsig_key=TEST_TSIG_KEY)
+
+ def test_broken_request(self):
+ # Message data too short
+ s = self.__sock
+ self.assertFalse(self.server.handle_request((self.__sock, None,
+ None, b'x' * 11)))
+ self.assertEqual((None, None), (s._sent_data, s._sent_addr))
+
+ # Opcode is not UPDATE
+ self.assertFalse(self.server.handle_request(
+ (self.__sock, None, None, create_msg(opcode=Opcode.QUERY()))))
+ self.assertEqual((None, None), (s._sent_data, s._sent_addr))
+
+ # TSIG verification error. We use UPDATE_DROP to signal check_session
+ # that no response should be given.
+ self.check_session(result=UPDATE_DROP, ipv6=False,
+ tsig_key=BAD_TSIG_KEY)
+
+ def test_socket_error(self):
+ # Have the faked socket raise an exception on sendto()
+ self.__sock._raise_on_send = True
+ # handle_request indicates the failure
+ self.assertFalse(self.server.handle_request((self.__sock, TEST_SERVER6,
+ TEST_SERVER4,
+ create_msg())))
+ # this check ensures sendto() was really attempted.
+ self.check_update_response(self.__sock._sent_data, Rcode.NOERROR())
+
+ def test_tcp_request(self):
+ # Right now TCP request is not supported.
+ s = self.__sock
+ s.proto = socket.IPPROTO_TCP
+ self.assertFalse(self.server.handle_request((s, TEST_SERVER6,
+ TEST_SERVER4,
+ create_msg())))
+ self.assertEqual((None, None), (s._sent_data, s._sent_addr))
+
+ def test_request_message(self):
+ '''Test if the request message stores RRs separately.'''
+ # Specify 'drop' so the passed message won't be modified.
+ self.__faked_result = UPDATE_DROP
+ # Put the same RR twice in the prerequisite section. We should see
+ # them as separate RRs.
+ dummy_record = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(),
+ RRTTL(0))
+ dummy_record.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS, "ns.example"))
+ self.server.handle_request((self.__sock, TEST_SERVER6, TEST_CLIENT6,
+ create_msg(prereq=[dummy_record,
+ dummy_record])))
+ num_rrsets = len(self.__req_message.get_section(SECTION_PREREQUISITE))
+ self.assertEqual(2, num_rrsets)
+
+ def check_session_msg(self, result, expect_recv=1):
+ '''Check post update communication with other modules.'''
+ # iff the update succeeds, b10-ddns should tell interested other
+ # modules the information about the update zone in the form of
+ # {'command': ['notify', {'zone_name': <updated_zone_name>,
+ # 'zone_class', <updated_zone_class>}]}
+ # and expect an answer by calling group_recvmsg().
+ #
+ # expect_recv indicates the expected number of calls to
+ # group_recvmsg(), which is normally 1, but can be 0 if send fails.
+ if result == UPDATE_SUCCESS:
+ self.assertEqual(1, len(self.__cc_session._sent_msg))
+ self.assertEqual(expect_recv, self.__cc_session._recvmsg_called)
+ sent_msg, sent_group = self.__cc_session._sent_msg[0]
+ sent_cmd = sent_msg['command']
+ self.assertEqual('Xfrout', sent_group)
+ self.assertEqual('notify', sent_cmd[0])
+ self.assertEqual(2, len(sent_cmd[1]))
+ self.assertEqual(TEST_ZONE_NAME.to_text(), sent_cmd[1]['zone_name'])
+ self.assertEqual(TEST_RRCLASS.to_text(), sent_cmd[1]['zone_class'])
+ else:
+ # for other result cases neither send nor recvmsg should be called.
+ self.assertEqual([], self.__cc_session._sent_msg)
+ self.assertEqual(0, self.__cc_session._recvmsg_called)
+
+ def test_session_msg(self):
+ '''Test post update communication with other modules.'''
+ # Normal cases, confirming communication takes place iff update
+ # succeeds
+ for r in [UPDATE_SUCCESS, UPDATE_ERROR, UPDATE_DROP]:
+ self.__cc_session.clear_msg()
+ self.check_session(result=r)
+ self.check_session_msg(r)
+
+ # Return an error from the remote module, which should be just ignored.
+ self.__cc_session.clear_msg()
+ self.__cc_session._answer_code = 1
+ self.check_session()
+ self.check_session_msg(UPDATE_SUCCESS)
+
+ # raise some exceptions from the faked session. Expected ones are
+ # simply (logged and) ignored
+ self.__cc_session.clear_msg()
+ self.__cc_session._recvmsg_exception = SessionTimeout('dummy timeout')
+ self.check_session()
+ self.check_session_msg(UPDATE_SUCCESS)
+
+ self.__cc_session.clear_msg()
+ self.__cc_session._recvmsg_exception = SessionError('dummy error')
+ self.check_session()
+ self.check_session_msg(UPDATE_SUCCESS)
+
+ self.__cc_session.clear_msg()
+ self.__cc_session._recvmsg_exception = ProtocolError('dummy perror')
+ self.check_session()
+ self.check_session_msg(UPDATE_SUCCESS)
+
+ # Similar to the previous cases, but sendmsg() raises, so there should
+ # be no call to recvmsg().
+ self.__cc_session.clear_msg()
+ self.__cc_session._sendmsg_exception = SessionError('send error')
+ self.check_session()
+ self.check_session_msg(UPDATE_SUCCESS, expect_recv=0)
+
+ # Unexpected exception will be propagated (and will terminate the
+ # server)
+ self.__cc_session.clear_msg()
+ self.__cc_session._sendmsg_exception = RuntimeError('unexpected')
+ self.assertRaises(RuntimeError, self.check_session)
+
+ def test_session_with_config(self):
+ '''Check a session with more relistic config setups
+
+ We don't have to explore various cases in detail in this test.
+ We're just checking if the expected configured objects are passed
+ to the session object.
+
+ '''
+
+ # reset the session class to the real one
+ self.server._UpdateSessionClass = isc.ddns.session.UpdateSession
+
+ # install all-drop ACL
+ new_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
+ 'class': TEST_RRCLASS_STR,
+ 'update_acl': [{'action': 'DROP'}] } ] }
+ answer = self.server.config_handler(new_config)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+
+ # check the result
+ self.check_session(UPDATE_DROP)
+
class TestMain(unittest.TestCase):
def setUp(self):
self._server = MyDDNSServer()
@@ -379,6 +855,8 @@ class TestMain(unittest.TestCase):
def __clear_socket(self):
self.__clear_called = True
+ # Get rid of the socket file too
+ self.__orig_clear()
def check_exception(self, ex):
'''Common test sequence to see if the given exception is caused.
@@ -414,6 +892,38 @@ class TestMain(unittest.TestCase):
self.assertRaises(BaseException, ddns.main, self._server)
self.assertTrue(self._server.exception_raised)
+class TestConfig(unittest.TestCase):
+ '''Test some simple config related things that don't need server. '''
+ def setUp(self):
+ self.__ccsession = MyCCSession()
+
+ def test_file_path(self):
+ # Check some common paths
+ self.assertEqual(os.environ["B10_FROM_BUILD"] + "/ddns_socket",
+ ddns.SOCKET_FILE)
+ self.assertEqual(os.environ["B10_FROM_SOURCE"] +
+ "/src/bin/ddns/ddns.spec", ddns.SPECFILE_LOCATION)
+ self.assertEqual(os.environ["B10_FROM_BUILD"] +
+ "/src/bin/auth/auth.spec",
+ ddns.AUTH_SPECFILE_LOCATION)
+
+ def test_get_datasrc_client(self):
+ # The test sqlite DB should contain the example.org zone.
+ rrclass, datasrc_client = ddns.get_datasrc_client(self.__ccsession)
+ self.assertEqual(RRClass.IN(), rrclass)
+ self.assertEqual(DataSourceClient.SUCCESS,
+ datasrc_client.find_zone(Name('example.org'))[0])
+
+ def test_get_datasrc_client_fail(self):
+ # DB file is in a non existent directory, and creatng the client
+ # will fail. get_datasrc_client will return a dummy client, which
+ # will subsequently make find_zone() fail.
+ self.__ccsession.auth_db_file = './notexistentdir/somedb.sqlite3'
+ rrclass, datasrc_client = ddns.get_datasrc_client(self.__ccsession)
+ self.assertEqual(RRClass.IN(), rrclass)
+ self.assertRaises(isc.datasrc.Error,
+ datasrc_client.find_zone, Name('example.org'))
+
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
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/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/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 d216f53..a1a11a0 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -2,8 +2,9 @@ PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
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)
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index 85732a1..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,55 +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.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])
- # Let's print this out before we redirect out stdout.
- 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.")
-
- # 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])
# 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
- 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/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/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 01254d4..b98996a 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -24,6 +24,7 @@ endif
B10_FROM_SOURCE=$(abs_top_srcdir) \
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
index dd74036..973fc8b 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/src/lib/asiolink/io_endpoint.h
@@ -168,8 +168,8 @@ public:
///
/// 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)
+/// - 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
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/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/datasrc/database.cc b/src/lib/datasrc/database.cc
index 7b271f1..358dce8 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -450,7 +450,8 @@ DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
const size_t remove_labels = name.getLabelCount() - origin_label_count;
// Go through all superdomains from the origin down searching for nodes
- // that indicate a delegation (.e. NS or DNAME).
+ // that indicate a delegation (.e. NS or DNAME). Note that we only check
+ // pure superdomains; delegation on an exact match will be detected later.
for (int i = remove_labels; i > 0; --i) {
const Name superdomain(name.split(i));
@@ -810,12 +811,14 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
const FoundIterator cni(found.second.find(RRType::CNAME()));
const FoundIterator wti(found.second.find(type));
- if (!is_origin && (options & FIND_GLUE_OK) == 0 &&
+ if (!is_origin && (options & FIND_GLUE_OK) == 0 && type != RRType::DS() &&
nsi != found.second.end()) {
// A NS RRset was found at the domain we were searching for. As it is
// not at the origin of the zone, it is a delegation and indicates that
// this zone is not authoritative for the data. Just return the
- // delegation information.
+ // delegation information, except:
+ // - when we are looking for glue records (FIND_GLUE_OK), or
+ // - when the query type is DS (which cancels the delegation)
return (logAndCreateResult(name, wildname, type, DELEGATION,
nsi->second,
wild ? DATASRC_DATABASE_WILDCARD_NS :
@@ -839,8 +842,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 +852,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/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 8e834cb..68a42dd 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -1747,7 +1747,7 @@ generateRRsetFromIterator(ZoneIterator* iterator, LoadCallback callback) {
}
void
-InMemoryZoneFinder::load(const string& filename) {
+InMemoryZoneFinder::load(const std::string& filename) {
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
arg(filename);
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/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index a0a9ca8..55d8052 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -142,9 +142,11 @@ const char* const TEST_RECORDS[][5] = {
{"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
{"delegation.example.org.", "NS", "3600", "",
"ns.delegation.example.org."},
- {"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
+ {"delegation.example.org.", "DS", "3600", "", "1 1 2 abcd"},
{"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
"20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+ {"delegation.example.org.", "RRSIG", "3600", "", "DS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
{"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
{"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
@@ -156,6 +158,16 @@ const char* const TEST_RECORDS[][5] = {
{"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},
+ // Insecure delegation (i.e., no DS at the delegation point)
+ {"insecdelegation.example.org.", "NS", "3600", "", "ns.example.com."},
+ {"insecdelegation.example.org.", "NSEC", "3600", "",
+ "dummy.example.org. NS NSEC"},
+ // and a DS under the zone cut. Such an RR shouldn't exist in a sane zone,
+ // but it could by error or some malicious attempt. It shouldn't confuse
+ // the implementation)
+ {"child.insecdelegation.example.org.", "DS", "3600", "", "DS 5 3 3600 "
+ "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
+
// Broken NS
{"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
{"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},
@@ -2201,6 +2213,48 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
DataSourceError);
}
+TYPED_TEST(DatabaseClientTest, findDS) {
+ // Type DS query is an exception to the general delegation case; the NS
+ // should be ignored and it should be treated just like normal
+ // authoritative data.
+
+ boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
+
+ // DS exists at the delegation point. It should be returned with result
+ // code of SUCCESS.
+ this->expected_rdatas_.push_back("1 1 2 abcd"),
+ this->expected_sig_rdatas_.push_back("DS 5 3 3600 20000101000000 "
+ "20000201000000 12345 example.org. "
+ "FAKEFAKEFAKE");
+ doFindTest(*finder, Name("delegation.example.org."),
+ RRType::DS(), RRType::DS(), this->rrttl_, ZoneFinder::SUCCESS,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_DEFAULT);
+
+ // DS doesn't exist at the delegation point. The result should be
+ // NXRRSET, and if DNSSEC is requested and the zone is NSEC-signed,
+ // the corresponding NSEC should be returned (normally with its RRSIG,
+ // but in this simplified test setup it's omitted in the test data).
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("dummy.example.org. NS NSEC");
+ this->expected_sig_rdatas_.clear();
+ doFindTest(*finder, Name("insecdelegation.example.org."),
+ RRType::DS(), RRType::NSEC(), this->rrttl_, ZoneFinder::NXRRSET,
+ this->expected_rdatas_, this->expected_sig_rdatas_,
+ ZoneFinder::RESULT_NSEC_SIGNED,
+ Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+
+ // Some insane case: DS under a zone cut. It's included in the DB, but
+ // shouldn't be visible via finder.
+ this->expected_rdatas_.clear();
+ this->expected_rdatas_.push_back("ns.example.com");
+ doFindTest(*finder, Name("child.insecdelegation.example.org"),
+ RRType::DS(), RRType::NS(), this->rrttl_,
+ ZoneFinder::DELEGATION, this->expected_rdatas_,
+ this->empty_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ Name("insecdelegation.example.org."), ZoneFinder::FIND_DNSSEC);
+}
+
TYPED_TEST(DatabaseClientTest, emptyDomain) {
boost::shared_ptr<DatabaseClient::Finder> finder(this->getFinder());
diff --git a/src/lib/dns/labelsequence.h b/src/lib/dns/labelsequence.h
index 6b10b67..b17eeb4 100644
--- a/src/lib/dns/labelsequence.h
+++ b/src/lib/dns/labelsequence.h
@@ -101,7 +101,7 @@ public:
/// \note No actual memory is changed, this operation merely updates the
/// internal pointers based on the offsets in the Name object.
///
- /// \exeption OutOfRange if i is greater than or equal to the number
+ /// \exception OutOfRange if i is greater than or equal to the number
/// of labels currently pointed to by this LabelSequence
///
/// \param i The number of labels to remove.
@@ -112,7 +112,7 @@ public:
/// \note No actual memory is changed, this operation merely updates the
/// internal pointers based on the offsets in the Name object.
///
- /// \exeption OutOfRange if i is greater than or equal to the number
+ /// \exception OutOfRange if i is greater than or equal to the number
/// of labels currently pointed to by this LabelSequence
///
/// \param i The number of labels to remove.
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index a9be8be..0a1625a 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -573,7 +573,11 @@ Message::clearSection(const Section section) {
if (section >= MessageImpl::NUM_SECTIONS) {
isc_throw(OutOfRange, "Invalid message section: " << section);
}
- impl_->rrsets_[section].clear();
+ if (section == Message::SECTION_QUESTION) {
+ impl_->questions_.clear();
+ } else {
+ impl_->rrsets_[section].clear();
+ }
impl_->counts_[section] = 0;
}
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index fdd4a20..f08f62c 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -209,12 +209,24 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
return (NULL);
}
- if (self->cppobj->getHeaderFlag(
+ try {
+ if (self->cppobj->getHeaderFlag(
static_cast<Message::HeaderFlag>(messageflag))) {
- Py_RETURN_TRUE;
- } else {
- Py_RETURN_FALSE;
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const isc::InvalidParameter& ip) {
+ PyErr_Clear();
+ PyErr_SetString(po_InvalidParameter, ip.what());
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.get_header_flag(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_header_flag()");
}
+ return (NULL);
}
PyObject*
@@ -240,12 +252,17 @@ Message_setHeaderFlag(s_Message* self, PyObject* args) {
} catch (const InvalidMessageOperation& imo) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
} catch (const isc::InvalidParameter& ip) {
PyErr_Clear();
PyErr_SetString(po_InvalidParameter, ip.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.set_header_flag(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.set_header_flag()");
}
+ return (NULL);
}
PyObject*
@@ -273,8 +290,14 @@ Message_setQid(s_Message* self, PyObject* args) {
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.get_qid(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.set_qid()");
}
+ return (NULL);
}
PyObject*
@@ -283,11 +306,14 @@ Message_getRcode(s_Message* self) {
return (createRcodeObject(self->cppobj->getRcode()));
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.get_rcode(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- return (NULL);
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_rcode()");
}
+ return (NULL);
}
PyObject*
@@ -301,8 +327,14 @@ Message_setRcode(s_Message* self, PyObject* args) {
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.set_rcode(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.set_rcode()");
}
+ return (NULL);
}
PyObject*
@@ -311,17 +343,14 @@ Message_getOpcode(s_Message* self) {
return (createOpcodeObject(self->cppobj->getOpcode()));
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
} catch (const exception& ex) {
- const string ex_what =
- "Failed to get message opcode: " + string(ex.what());
+ const string ex_what = "Error in Message.get_opcode(): " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
- return (NULL);
} catch (...) {
PyErr_SetString(po_IscException,
- "Unexpected exception getting opcode from message");
- return (NULL);
+ "Unexpected exception in Message.get_opcode()");
}
+ return (NULL);
}
PyObject*
@@ -335,8 +364,14 @@ Message_setOpcode(s_Message* self, PyObject* args) {
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.set_opcode(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.set_opcode()");
}
+ return (NULL);
}
PyObject*
@@ -348,12 +383,11 @@ Message_getEDNS(s_Message* self) {
try {
return (createEDNSObject(*src));
} catch (const exception& ex) {
- const string ex_what =
- "Failed to get EDNS from message: " + string(ex.what());
+ const string ex_what = "Error in Message.get_edns(): " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(PyExc_SystemError,
- "Unexpected failure getting EDNS from message");
+ "Unexpected exception in Message.get_edns()");
}
return (NULL);
}
@@ -369,8 +403,14 @@ Message_setEDNS(s_Message* self, PyObject* args) {
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.set_edns(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.set_edns()");
}
+ return (NULL);
}
PyObject*
@@ -386,13 +426,11 @@ Message_getTSIGRecord(s_Message* self) {
} catch (const InvalidMessageOperation& ex) {
PyErr_SetString(po_InvalidMessageOperation, ex.what());
} catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in getting TSIGRecord from message: " +
- string(ex.what());
+ const string ex_what = "Error in Message.get_tsig_record(): " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
- "getting TSIGRecord from message");
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_tsig_record()");
}
return (NULL);
}
@@ -411,8 +449,14 @@ Message_getRRCount(s_Message* self, PyObject* args) {
static_cast<Message::Section>(section))));
} catch (const isc::OutOfRange& ex) {
PyErr_SetString(PyExc_OverflowError, ex.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.get_rr_count(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_rr_count()");
}
+ return (NULL);
}
// This is a helper templated class commonly used for getQuestion and
@@ -453,13 +497,11 @@ Message_getQuestion(PyObject* po_self, PyObject*) {
} catch (const InvalidMessageSection& ex) {
PyErr_SetString(po_InvalidMessageSection, ex.what());
} catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in Message.get_question: " +
- string(ex.what());
+ const string ex_what = "Error in Message.get_question(): " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in Message.get_question");
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_question()");
}
return (NULL);
}
@@ -489,13 +531,11 @@ Message_getSection(PyObject* po_self, PyObject* args) {
} catch (const InvalidMessageSection& ex) {
PyErr_SetString(po_InvalidMessageSection, ex.what());
} catch (const exception& ex) {
- const string ex_what =
- "Unexpected failure in Message.get_section: " +
- string(ex.what());
+ const string ex_what = "Error in Message.get_section(): " + string(ex.what());
PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(PyExc_SystemError,
- "Unexpected failure in Message.get_section");
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.get_section()");
}
return (NULL);
}
@@ -513,9 +553,20 @@ Message_addQuestion(s_Message* self, PyObject* args) {
return (NULL);
}
- self->cppobj->addQuestion(PyQuestion_ToQuestion(question));
-
- Py_RETURN_NONE;
+ try {
+ self->cppobj->addQuestion(PyQuestion_ToQuestion(question));
+ Py_RETURN_NONE;
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_Clear();
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.add_question(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.add_question()");
+ }
+ return (NULL);
}
PyObject*
@@ -534,36 +585,45 @@ Message_addRRset(s_Message* self, PyObject* args) {
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 (const exception& ex) {
+ const string ex_what = "Error in Message.add_rrset(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(po_IscException,
- "Unexpected exception in adding RRset");
- return (NULL);
+ "Unexpected exception in Message.add_rrset()");
}
+ return (NULL);
}
PyObject*
Message_clear(s_Message* self, PyObject* args) {
int i;
- if (PyArg_ParseTuple(args, "i", &i)) {
- PyErr_Clear();
- if (i == Message::PARSE) {
- self->cppobj->clear(Message::PARSE);
- Py_RETURN_NONE;
- } else if (i == Message::RENDER) {
- self->cppobj->clear(Message::RENDER);
- Py_RETURN_NONE;
- } else {
- PyErr_SetString(PyExc_TypeError,
- "Message mode must be Message.PARSE or Message.RENDER");
- return (NULL);
+
+ try {
+ if (PyArg_ParseTuple(args, "i", &i)) {
+ PyErr_Clear();
+ if (i == Message::PARSE) {
+ self->cppobj->clear(Message::PARSE);
+ Py_RETURN_NONE;
+ } else if (i == Message::RENDER) {
+ self->cppobj->clear(Message::RENDER);
+ Py_RETURN_NONE;
+ } else {
+ PyErr_SetString(PyExc_TypeError,
+ "Message mode must be Message.PARSE or Message.RENDER");
+ return (NULL);
+ }
}
- } else {
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.clear(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.clear()");
}
+ return (NULL);
}
PyObject*
@@ -579,21 +639,34 @@ Message_clearSection(PyObject* pyself, PyObject* args) {
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 (const exception& ex) {
+ const string ex_what = "Error in Message.clear_section(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
PyErr_SetString(po_IscException,
- "Unexpected exception in adding RRset");
- return (NULL);
+ "Unexpected exception in Message.clear_section()");
}
+ return (NULL);
}
PyObject*
Message_makeResponse(s_Message* self) {
- self->cppobj->makeResponse();
- Py_RETURN_NONE;
+ try {
+ self->cppobj->makeResponse();
+ Py_RETURN_NONE;
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_Clear();
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.make_response(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.make_response()");
+ }
+ return (NULL);
}
PyObject*
@@ -604,11 +677,14 @@ Message_toText(s_Message* self) {
} catch (const InvalidMessageOperation& imo) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.to_text(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(po_IscException, "Unexpected exception");
- return (NULL);
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.to_text()");
}
+ return (NULL);
}
PyObject*
@@ -639,22 +715,18 @@ Message_toWire(s_Message* self, PyObject* args) {
} catch (const InvalidMessageOperation& imo) {
PyErr_Clear();
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
} catch (const TSIGContextError& ex) {
// toWire() with a TSIG context can fail due to this if the
// python program has a bug.
PyErr_SetString(po_TSIGContextError, ex.what());
- return (NULL);
- } catch (const std::exception& ex) {
- // Other exceptions should be rare (most likely an implementation
- // bug)
- PyErr_SetString(po_TSIGContextError, ex.what());
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what = "Error in Message.to_wire(): " + string(ex.what());
+ PyErr_SetString(po_TSIGContextError, ex_what.c_str());
} catch (...) {
- PyErr_SetString(PyExc_RuntimeError,
- "Unexpected C++ exception in Message.to_wire");
- return (NULL);
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.to_wire()");
}
+ return (NULL);
}
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
@@ -682,29 +754,22 @@ Message_fromWire(PyObject* pyself, PyObject* args) {
Py_RETURN_NONE;
} catch (const InvalidMessageOperation& imo) {
PyErr_SetString(po_InvalidMessageOperation, imo.what());
- return (NULL);
} catch (const DNSMessageFORMERR& dmfe) {
PyErr_SetString(po_DNSMessageFORMERR, dmfe.what());
- return (NULL);
} catch (const DNSMessageBADVERS& dmfe) {
PyErr_SetString(po_DNSMessageBADVERS, dmfe.what());
- return (NULL);
} catch (const MessageTooShort& mts) {
PyErr_SetString(po_MessageTooShort, mts.what());
- return (NULL);
} catch (const InvalidBufferPosition& ex) {
PyErr_SetString(po_DNSMessageFORMERR, ex.what());
- return (NULL);
} catch (const exception& ex) {
- const string ex_what =
- "Error in Message.from_wire: " + string(ex.what());
- PyErr_SetString(PyExc_RuntimeError, ex_what.c_str());
- return (NULL);
+ const string ex_what = "Error in Message.from_wire(): " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
} catch (...) {
- PyErr_SetString(PyExc_RuntimeError,
- "Unexpected exception in Message.from_wire");
- return (NULL);
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in Message.from_wire()");
}
+ return (NULL);
}
PyErr_SetString(PyExc_TypeError,
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index 6758d0e..c24d24d 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -115,7 +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);
+Py_hash_t Name_hash(PyObject* py_self);
PyMethodDef Name_methods[] = {
{ "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
@@ -520,7 +520,7 @@ Name_isWildCard(s_Name* self) {
}
}
-long
+Py_hash_t
Name_hash(PyObject* pyself) {
s_Name* const self = static_cast<s_Name*>(pyself);
return (LabelSequence(*self->cppobj).getHash(false));
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 8092b08..e9e9359 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -43,6 +43,11 @@ extern PyObject* po_DNSMessageBADVERS;
int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+
+// Short term workaround for unifying the return type of tp_hash
+#if PY_MINOR_VERSION < 2
+typedef long Py_hash_t;
+#endif
} // namespace python
} // namespace dns
} // namespace isc
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index 2c3dae6..b94dc02 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -52,7 +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);
+Py_hash_t RRClass_hash(PyObject* pyself);
// Static function for direct class creation
PyObject* RRClass_IN(s_RRClass *self);
@@ -265,7 +265,7 @@ PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
-long
+Py_hash_t
RRClass_hash(PyObject* pyself) {
s_RRClass* const self = static_cast<s_RRClass*>(pyself);
return (self->cppobj->getCode());
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 1ec0e99..6f32b11 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -118,6 +118,11 @@ class MessageTest(unittest.TestCase):
self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_AD))
self.assertFalse(self.r.get_header_flag(Message.HEADERFLAG_CD))
+ # 0 passed as flag should raise
+ self.assertRaises(InvalidParameter, self.r.get_header_flag, 0)
+ # unused bit
+ self.assertRaises(InvalidParameter, self.r.get_header_flag, 0x80000000)
+
self.r.set_header_flag(Message.HEADERFLAG_QR)
self.assertTrue(self.r.get_header_flag(Message.HEADERFLAG_QR))
@@ -267,6 +272,15 @@ class MessageTest(unittest.TestCase):
self.assertEqual(1, sys.getrefcount(self.r.get_question()))
self.assertEqual(1, sys.getrefcount(self.r.get_question()[0]))
+ # Message.add_question() called in non-RENDER mode should assert
+ self.r.clear(Message.PARSE)
+ self.assertRaises(InvalidMessageOperation, self.r.add_question, q)
+
+ def test_make_response(self):
+ # Message.make_response() called in non-PARSE mode should assert
+ self.r.clear(Message.RENDER)
+ self.assertRaises(InvalidMessageOperation, self.r.make_response)
+
def test_add_rrset(self):
self.assertRaises(TypeError, self.r.add_rrset, "wrong")
self.assertRaises(TypeError, self.r.add_rrset)
@@ -295,6 +309,7 @@ class MessageTest(unittest.TestCase):
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))
+ self.assertEqual(0, len(self.r.get_question()))
def test_clear_section(self):
for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index c1ece52..59a5887 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -119,7 +119,7 @@ Generic::Generic(isc::util::InputBuffer& buffer, size_t rdata_len) {
impl_ = new GenericImpl(data);
}
-Generic::Generic(const string& rdata_string) {
+Generic::Generic(const std::string& rdata_string) {
istringstream iss(rdata_string);
string unknown_mark;
iss >> unknown_mark;
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index 4eb72bc..9ef887f 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -74,25 +74,28 @@ struct TSIG::TSIGImpl {
/// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
/// \endcode
/// where
-/// - <Alg> is a valid textual representation of domain name.
-/// - <Time> is an unsigned 48-bit decimal integer.
-/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned 16-bit decimal
+/// - <Alg> is a valid textual representation of domain name.
+/// - <Time> is an unsigned 48-bit decimal integer.
+/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned
+/// 16-bit decimal
/// integer.
-/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic for
-/// the Error field specified in RFC2845. Currently, "BADSIG", "BADKEY",
+/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic
+/// for the Error field specified in RFC2845. Currently, "BADSIG", "BADKEY",
/// and "BADTIME" are supported (case sensitive). In future versions
/// other representations that are compatible with the DNS RCODE will be
/// supported.
-/// - <MAC> and <OtherData> is a BASE-64 encoded string that does not contain
-/// space characters.
-/// When <MACSize> and <OtherLen> is 0, <MAC> and <OtherData> must not
-/// appear in \c tsgi_str, respectively.
-/// - The decoded data of <MAC> is <MACSize> bytes of binary stream.
-/// - The decoded data of <OtherData> is <OtherLen> bytes of binary stream.
+/// - <MAC> and <OtherData> is a BASE-64 encoded string that does
+/// not contain space characters.
+/// When <MACSize> and <OtherLen> is 0, <MAC> and
+/// <OtherData> must not appear in \c tsig_str, respectively.
+/// - The decoded data of <MAC> is <MACSize> bytes of binary
+/// stream.
+/// - The decoded data of <OtherData> is <OtherLen> bytes of
+/// binary stream.
///
/// An example of valid string is:
/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
-/// In this example <OtherData> is missing because <OtherLen> is 0.
+/// In this example <OtherData> is missing because <OtherLen> is 0.
///
/// Note that RFC2845 does not define the standard presentation format
/// of %TSIG RR, so the above syntax is implementation specific.
@@ -101,10 +104,10 @@ struct TSIG::TSIGImpl {
///
/// <b>Exceptions</b>
///
-/// If <Alg> is not a valid domain name, a corresponding exception from
+/// If <Alg> is not a valid domain name, a corresponding exception from
/// the \c Name class will be thrown;
-/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an exception
-/// of class \c isc::BadValue will be thrown;
+/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an
+/// exception of class \c isc::BadValue will be thrown;
/// if %any of the other bullet points above is not met, an exception of
/// class \c InvalidRdataText will be thrown.
/// This constructor internally involves resource allocation, and if it fails
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
index 65378a1..3d13a9e 100644
--- a/src/lib/dns/rdata/ch_3/a_1.cc
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -27,7 +27,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-A::A(const string&) {
+A::A(const std::string&) {
// TBD
}
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/rdata/generic/dlv_32769.cc b/src/lib/dns/rdata/generic/dlv_32769.cc
index 9887aa8..a3db998 100644
--- a/src/lib/dns/rdata/generic/dlv_32769.cc
+++ b/src/lib/dns/rdata/generic/dlv_32769.cc
@@ -34,7 +34,7 @@ using namespace isc::dns::rdata::generic::detail;
/// \brief Constructor from string.
///
/// A copy of the implementation object is allocated and constructed.
-DLV::DLV(const string& ds_str) :
+DLV::DLV(const std::string& ds_str) :
impl_(new DLVImpl(ds_str))
{}
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index 7bdbd05..054ac18 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.cc
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -51,7 +51,7 @@ struct DNSKEYImpl {
const vector<uint8_t> keydata_;
};
-DNSKEY::DNSKEY(const string& dnskey_str) :
+DNSKEY::DNSKEY(const std::string& dnskey_str) :
impl_(NULL)
{
istringstream iss(dnskey_str);
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
index 20b62dc..4234f9d 100644
--- a/src/lib/dns/rdata/generic/ds_43.cc
+++ b/src/lib/dns/rdata/generic/ds_43.cc
@@ -31,7 +31,7 @@ using namespace isc::dns::rdata::generic::detail;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-DS::DS(const string& ds_str) :
+DS::DS(const std::string& ds_str) :
impl_(new DSImpl(ds_str))
{}
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
index 45f4209..b1aeaa1 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.cc
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -37,7 +37,7 @@ using namespace isc::dns::characterstr;
// BEGIN_RDATA_NAMESPACE
-HINFO::HINFO(const string& hinfo_str) {
+HINFO::HINFO(const std::string& hinfo_str) {
string::const_iterator input_iterator = hinfo_str.begin();
cpu_ = getNextCharacterString(hinfo_str, input_iterator);
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index b569d91..89f188a 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -64,7 +64,7 @@ struct NSEC3Impl {
const vector<uint8_t> typebits_;
};
-NSEC3::NSEC3(const string& nsec3_str) :
+NSEC3::NSEC3(const std::string& nsec3_str) :
impl_(NULL)
{
istringstream iss(nsec3_str);
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index ac09b57..6614bdc 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -46,7 +46,7 @@ struct NSEC3PARAMImpl {
const vector<uint8_t> salt_;
};
-NSEC3PARAM::NSEC3PARAM(const string& nsec3param_str) :
+NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
impl_(NULL)
{
istringstream iss(nsec3param_str);
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index 08825db..aeb6da8 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -49,7 +49,7 @@ struct NSECImpl {
vector<uint8_t> typebits_;
};
-NSEC::NSEC(const string& nsec_str) :
+NSEC::NSEC(const std::string& nsec_str) :
impl_(NULL)
{
istringstream iss(nsec_str);
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index 62cfc17..d64effb 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -27,7 +27,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-OPT::OPT(const string&) {
+OPT::OPT(const std::string&) {
isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
}
diff --git a/src/lib/dns/rdata/generic/ptr_12.cc b/src/lib/dns/rdata/generic/ptr_12.cc
index 86ddeb4..b76fc7f 100644
--- a/src/lib/dns/rdata/generic/ptr_12.cc
+++ b/src/lib/dns/rdata/generic/ptr_12.cc
@@ -28,7 +28,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-PTR::PTR(const string& type_str) :
+PTR::PTR(const std::string& type_str) :
ptr_name_(type_str)
{}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index 59ff030..e0137b9 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -72,7 +72,7 @@ struct RRSIGImpl {
const vector<uint8_t> signature_;
};
-RRSIG::RRSIG(const string& rrsig_str) :
+RRSIG::RRSIG(const std::string& rrsig_str) :
impl_(NULL)
{
istringstream iss(rrsig_str);
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index e473bca..e70db0f 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -41,7 +41,7 @@ SOA::SOA(InputBuffer& buffer, size_t) :
buffer.readData(numdata_, sizeof(numdata_));
}
-SOA::SOA(const string& soastr) :
+SOA::SOA(const std::string& soastr) :
mname_("."), rname_(".") // quick hack workaround
{
istringstream iss(soastr);
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index 6320fd9..6fa8609 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -80,7 +80,7 @@ SSHFP::SSHFP(const std::string& sshfp_str)
decodeHex(fingerprintbuf.str(), fingerprint_);
}
-SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const string& fingerprint)
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint)
{
if ((algorithm < 1) || (algorithm > 2)) {
isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
index 65378a1..3d13a9e 100644
--- a/src/lib/dns/rdata/hs_4/a_1.cc
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -27,7 +27,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-A::A(const string&) {
+A::A(const std::string&) {
// TBD
}
diff --git a/src/lib/dns/rdata/in_1/a_1.cc b/src/lib/dns/rdata/in_1/a_1.cc
index fa46f90..3b15a4c 100644
--- a/src/lib/dns/rdata/in_1/a_1.cc
+++ b/src/lib/dns/rdata/in_1/a_1.cc
@@ -34,7 +34,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-A::A(const string& addrstr) {
+A::A(const std::string& addrstr) {
// RFC1035 states textual representation of IN/A RDATA is
// "four decimal numbers separated by dots without any embedded spaces".
// This is exactly what inet_pton() accepts for AF_INET. In particular,
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.cc b/src/lib/dns/rdata/in_1/aaaa_28.cc
index e9fc122..ce49a04 100644
--- a/src/lib/dns/rdata/in_1/aaaa_28.cc
+++ b/src/lib/dns/rdata/in_1/aaaa_28.cc
@@ -34,7 +34,7 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-AAAA::AAAA(const string& addrstr) {
+AAAA::AAAA(const std::string& addrstr) {
if (inet_pton(AF_INET6, addrstr.c_str(), &addr_) != 1) {
isc_throw(InvalidRdataText,
"IN/AAAA RDATA construction from text failed: "
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
index f0c4aca..7745161 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.cc
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -47,7 +47,7 @@ using namespace isc::util;
/// < n octets > Digest (length depends on digest type)
/// If the data is less than 3 octets (i.e. it cannot contain id type code and
/// digest type code), an exception of class \c InvalidRdataLength is thrown.
-DHCID::DHCID(const string& dhcid_str) {
+DHCID::DHCID(const std::string& dhcid_str) {
istringstream iss(dhcid_str);
stringbuf digestbuf;
diff --git a/src/lib/dns/rdata/in_1/srv_33.cc b/src/lib/dns/rdata/in_1/srv_33.cc
index 93b5d4d..a1a3909 100644
--- a/src/lib/dns/rdata/in_1/srv_33.cc
+++ b/src/lib/dns/rdata/in_1/srv_33.cc
@@ -52,22 +52,22 @@ struct SRVImpl {
/// \code <Priority> <Weight> <Port> <Target>
/// \endcode
/// where
-/// - <Priority>, <Weight>, and <Port> are an unsigned 16-bit decimal
-/// integer.
-/// - <Target> is a valid textual representation of domain name.
+/// - <Priority>, <Weight>, and <Port> are an unsigned
+/// 16-bit decimal integer.
+/// - <Target> is a valid textual representation of domain name.
///
/// An example of valid string is:
/// \code "1 5 1500 example.com." \endcode
///
/// <b>Exceptions</b>
///
-/// If <Target> is not a valid domain name, a corresponding exception from
-/// the \c Name class will be thrown;
+/// If <Target> is not a valid domain name, a corresponding exception
+/// from the \c Name class will be thrown;
/// if %any of the other bullet points above is not met, an exception of
/// class \c InvalidRdataText will be thrown.
/// This constructor internally involves resource allocation, and if it fails
/// a corresponding standard exception will be thrown.
-SRV::SRV(const string& srv_str) :
+SRV::SRV(const std::string& srv_str) :
impl_(NULL)
{
istringstream iss(srv_str);
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index a28e5cf..ac5823c 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -30,7 +30,7 @@ using namespace isc::util;
namespace isc {
namespace dns {
-RRClass::RRClass(const string& classstr) {
+RRClass::RRClass(const std::string& classstr) {
classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
}
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 8b01e41..f7f3a1a 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -224,7 +224,7 @@ RRParamRegistry::getRegistry() {
}
void
-RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
RdataFactoryPtr rdata_factory)
{
bool type_added = false;
@@ -242,8 +242,8 @@ RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
}
void
-RRParamRegistry::add(const string& typecode_string, uint16_t typecode,
- const string& classcode_string, uint16_t classcode,
+RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode,
+ const std::string& classcode_string, uint16_t classcode,
RdataFactoryPtr rdata_factory)
{
// Rollback logic on failure is complicated. If adding the new type or
@@ -470,7 +470,7 @@ RRParamRegistry::codeToClassText(uint16_t code) const {
RdataPtr
RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
- const string& rdata_string)
+ const std::string& rdata_string)
{
// If the text indicates that it's rdata of an "unknown" type (beginning
// with '\# n'), parse it that way. (TBD)
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index ecd8cc6..49c63be 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -28,7 +28,7 @@ using namespace isc::util;
namespace isc {
namespace dns {
-RRTTL::RRTTL(const string& ttlstr) {
+RRTTL::RRTTL(const std::string& ttlstr) {
// Some systems (at least gcc-4.4) flow negative values over into
// unsigned integer, where older systems failed to parse. We want
// that failure here, so we extract into int64 and check the value
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index af077d4..4ef4e67 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -31,7 +31,7 @@ using isc::dns::RRType;
namespace isc {
namespace dns {
-RRType::RRType(const string& typestr) {
+RRType::RRType(const std::string& typestr) {
typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
}
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index c5dd3ed..f30a2ac 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -406,6 +406,8 @@ TEST_F(MessageTest, clearQuestionSection) {
message_render.clearSection(Message::SECTION_QUESTION);
EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
+ EXPECT_TRUE(message_render.beginQuestion() ==
+ message_render.endQuestion());
}
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index b82eb1b..fb3aed7 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -2,6 +2,7 @@ SUBDIRS = . compiler tests
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTOP_BUILDDIR=\"${abs_top_builddir}\"
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/log/compiler/message.cc b/src/lib/log/compiler/message.cc
index 66dc9c7..86c5f20 100644
--- a/src/lib/log/compiler/message.cc
+++ b/src/lib/log/compiler/message.cc
@@ -58,14 +58,14 @@ static const char* VERSION = "1.0-0";
/// \b Invocation<BR>
/// The program is invoked with the command:
///
-/// <tt>message [-v | -h | -p | -d <dir> | <message-file>]</tt>
+/// <tt>message [-v | -h | -p | -d <dir> | <message-file>]</tt>
///
/// It reads the message file and writes out two files of the same
/// name in the current working directory (unless -d is used) but
/// with extensions of .h and .cc, or .py if -p is used.
///
/// -v causes it to print the version number and exit. -h prints a help
-/// message (and exits). -p sets the output to python. -d <dir> will make
+/// message (and exits). -p sets the output to python. -d <dir> will make
/// it write the output file(s) to dir instead of current working
/// directory
@@ -119,9 +119,9 @@ currentTime() {
/// \brief Create Header Sentinel
///
-/// Given the name of a file, create an #ifdef sentinel name. The name is
-/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
-/// extension less the leading period. The sentinel will be upper-case.
+/// Given the name of a file, create an \#ifdef sentinel name. The name is
+/// __<name>_<ext>, where <name> is the name of the file, and <ext>
+/// is the extension less the leading period. The sentinel will be upper-case.
///
/// \param file Filename object representing the file.
///
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/logger.cc b/src/lib/log/logger.cc
index d10e979..fef5627 100644
--- a/src/lib/log/logger.cc
+++ b/src/lib/log/logger.cc
@@ -179,6 +179,13 @@ Logger::fatal(const isc::log::MessageID& ident) {
}
}
+// Replace the interprocess synchronization object
+
+void
+Logger::setInterprocessSync(isc::util::InterprocessSync* sync) {
+ getLoggerPtr()->setInterprocessSync(sync);
+}
+
// Comparison (testing only)
bool
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
index 5715bc4..6405488 100644
--- a/src/lib/log/logger.h
+++ b/src/lib/log/logger.h
@@ -25,6 +25,7 @@
#include <log/message_types.h>
#include <log/log_formatter.h>
+#include <util/interprocess_sync.h>
namespace isc {
namespace log {
@@ -98,6 +99,17 @@ public:
{}
};
+/// \brief Bad Interprocess Sync
+///
+/// Exception thrown if a bad InterprocessSync object (such as NULL) is
+/// used.
+class BadInterprocessSync : public isc::Exception {
+public:
+ BadInterprocessSync(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
/// \brief Logger Class
///
/// This class is the main class used for logging. Use comprises:
@@ -237,6 +249,17 @@ public:
/// \param ident Message identification.
Formatter fatal(const MessageID& ident);
+ /// \brief Replace the interprocess synchronization object
+ ///
+ /// If this method is called with NULL as the argument, it throws a
+ /// BadInterprocessSync exception.
+ ///
+ /// \param sync The logger uses this synchronization object for
+ /// synchronizing output of log messages. It should be deletable and
+ /// the ownership is transferred to the logger. If NULL is passed,
+ /// a BadInterprocessSync exception is thrown.
+ void setInterprocessSync(isc::util::InterprocessSync* sync);
+
/// \brief Equality
///
/// Check if two instances of this logger refer to the same stream.
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
index 046da13..2d6c0f4 100644
--- a/src/lib/log/logger_impl.cc
+++ b/src/lib/log/logger_impl.cc
@@ -32,12 +32,14 @@
#include <log/message_types.h>
#include <util/strutil.h>
+#include <util/interprocess_sync_file.h>
// Note: as log4cplus and the BIND 10 logger have many concepts in common, and
// thus many similar names, to disambiguate types we don't "use" the log4cplus
// namespace: instead, all log4cplus types are explicitly qualified.
using namespace std;
+using namespace isc::util;
namespace isc {
namespace log {
@@ -47,14 +49,17 @@ namespace log {
// one compiler requires that all member variables be constructed before the
// constructor is run, but log4cplus::Logger (the type of logger_) has no
// default constructor.
-LoggerImpl::LoggerImpl(const string& name) : name_(expandLoggerName(name)),
- logger_(log4cplus::Logger::getInstance(name_))
+LoggerImpl::LoggerImpl(const string& name) :
+ name_(expandLoggerName(name)),
+ logger_(log4cplus::Logger::getInstance(name_)),
+ sync_(new InterprocessSyncFile("logger"))
{
}
// Destructor. (Here because of virtual declaration.)
LoggerImpl::~LoggerImpl() {
+ delete sync_;
}
// Set the severity for logging.
@@ -102,8 +107,30 @@ LoggerImpl::lookupMessage(const MessageID& ident) {
MessageDictionary::globalDictionary().getText(ident)));
}
+// Replace the interprocess synchronization object
+
+void
+LoggerImpl::setInterprocessSync(isc::util::InterprocessSync* sync) {
+ if (sync == NULL) {
+ isc_throw(BadInterprocessSync,
+ "NULL was passed to setInterprocessSync()");
+ }
+
+ delete sync_;
+ sync_ = sync;
+}
+
void
LoggerImpl::outputRaw(const Severity& severity, const string& message) {
+ // Use an interprocess sync locker for mutual exclusion from other
+ // processes to avoid log messages getting interspersed.
+
+ InterprocessSyncLocker locker(*sync_);
+
+ if (!locker.lock()) {
+ LOG4CPLUS_ERROR(logger_, "Unable to lock logger lockfile");
+ }
+
switch (severity) {
case DEBUG:
LOG4CPLUS_DEBUG(logger_, message);
@@ -124,6 +151,10 @@ LoggerImpl::outputRaw(const Severity& severity, const string& message) {
case FATAL:
LOG4CPLUS_FATAL(logger_, message);
}
+
+ if (!locker.unlock()) {
+ LOG4CPLUS_ERROR(logger_, "Unable to unlock logger lockfile");
+ }
}
} // namespace log
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
index 90bd41a..10d3db4 100644
--- a/src/lib/log/logger_impl.h
+++ b/src/lib/log/logger_impl.h
@@ -32,6 +32,8 @@
#include <log/logger_level_impl.h>
#include <log/message_types.h>
+#include <util/interprocess_sync.h>
+
namespace isc {
namespace log {
@@ -167,6 +169,17 @@ public:
/// This gets you the unformatted text of message for given ID.
std::string* lookupMessage(const MessageID& id);
+ /// \brief Replace the interprocess synchronization object
+ ///
+ /// If this method is called with NULL as the argument, it throws a
+ /// BadInterprocessSync exception.
+ ///
+ /// \param sync The logger uses this synchronization object for
+ /// synchronizing output of log messages. It should be deletable and
+ /// the ownership is transferred to the logger implementation.
+ /// If NULL is passed, a BadInterprocessSync exception is thrown.
+ void setInterprocessSync(isc::util::InterprocessSync* sync);
+
/// \brief Equality
///
/// Check if two instances of this logger refer to the same stream.
@@ -178,8 +191,9 @@ public:
}
private:
- std::string name_; ///< Full name of this logger
- log4cplus::Logger logger_; ///< Underlying log4cplus logger
+ std::string name_; ///< Full name of this logger
+ log4cplus::Logger logger_; ///< Underlying log4cplus logger
+ isc::util::InterprocessSync* sync_;
};
} // namespace log
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index 8a8a36b..8431c2e 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -28,6 +28,7 @@
#include <log/message_initializer.h>
#include <log/message_reader.h>
#include <log/message_types.h>
+#include "util/interprocess_sync_null.h"
using namespace std;
@@ -148,6 +149,13 @@ LoggerManager::readLocalMessageFile(const char* file) {
MessageDictionary& dictionary = MessageDictionary::globalDictionary();
MessageReader reader(&dictionary);
+
+ // Turn off use of any lock files. This is because this logger can
+ // be used by standalone programs which may not have write access to
+ // the local state directory (to create lock files). So we switch to
+ // using a null interprocess sync object here.
+ logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+
try {
logger.info(LOG_READING_LOCAL_FILE).arg(file);
diff --git a/src/lib/log/logger_unittest_support.cc b/src/lib/log/logger_unittest_support.cc
index a0969be..4f02b07 100644
--- a/src/lib/log/logger_unittest_support.cc
+++ b/src/lib/log/logger_unittest_support.cc
@@ -160,6 +160,9 @@ void initLogger(isc::log::Severity severity, int dbglevel) {
// Set the local message file
const char* localfile = getenv("B10_LOGGER_LOCALMSG");
+ // Set a directory for creating lockfiles when running tests
+ setenv("B10_LOCKFILE_DIR_FROM_BUILD", TOP_BUILDDIR, 1);
+
// Initialize logging
initLogger(root, isc::log::DEBUG, isc::log::MAX_DEBUG_LEVEL, localfile);
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
index deb8232..3bfc56c 100644
--- a/src/lib/log/message_dictionary.cc
+++ b/src/lib/log/message_dictionary.cc
@@ -29,7 +29,7 @@ MessageDictionary::~MessageDictionary() {
// Add message and note if ID already exists
bool
-MessageDictionary::add(const string& ident, const string& text) {
+MessageDictionary::add(const std::string& ident, const std::string& text) {
Dictionary::iterator i = dictionary_.find(ident);
bool not_found = (i == dictionary_.end());
if (not_found) {
@@ -44,7 +44,7 @@ MessageDictionary::add(const string& ident, const string& text) {
// Add message and note if ID does not already exist
bool
-MessageDictionary::replace(const string& ident, const string& text) {
+MessageDictionary::replace(const std::string& ident, const std::string& text) {
Dictionary::iterator i = dictionary_.find(ident);
bool found = (i != dictionary_.end());
if (found) {
@@ -87,7 +87,7 @@ MessageDictionary::load(const char* messages[]) {
// output.
const string&
-MessageDictionary::getText(const string& ident) const {
+MessageDictionary::getText(const std::string& ident) const {
static const string empty("");
Dictionary::const_iterator i = dictionary_.find(ident);
if (i == dictionary_.end()) {
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index cd6caf2..8b9d58a 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -38,6 +38,9 @@ public:
/// \brief Constructor
///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
/// \param id Message identification.
/// \param lineno Line number on which error occurred (if > 0).
MessageException(const char* file, size_t line, const char* what,
@@ -51,6 +54,9 @@ public:
/// \brief Constructor
///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
/// \param id Message identification.
/// \param arg1 First message argument.
/// \param lineno Line number on which error occurred (if > 0).
@@ -66,6 +72,9 @@ public:
/// \brief Constructor
///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
/// \param id Message identification.
/// \param arg1 First message argument.
/// \param arg2 Second message argument.
diff --git a/src/lib/log/tests/.gitignore b/src/lib/log/tests/.gitignore
index 41b863b..b0e45b9 100644
--- a/src/lib/log/tests/.gitignore
+++ b/src/lib/log/tests/.gitignore
@@ -6,6 +6,10 @@
/initializer_unittests_2
/local_file_test.sh
/logger_example
+/logger_lock_test
+/logger_lock_test.sh
+/log_test_messages.cc
+/log_test_messages.h
/run_unittests
/severity_test.sh
/tempdir.h
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 6f3d768..9742037 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -12,6 +12,13 @@ endif
CLEANFILES = *.gcno *.gcda
+EXTRA_DIST = log_test_messages.mes
+BUILT_SOURCES = log_test_messages.h log_test_messages.cc
+log_test_messages.h log_test_messages.cc: log_test_messages.mes
+ $(AM_V_GEN) $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/log/tests/log_test_messages.mes
+
+CLEANFILES += log_test_messages.h log_test_messages.cc
+
noinst_PROGRAMS = logger_example
logger_example_SOURCES = logger_example.cc
logger_example_CPPFLAGS = $(AM_CPPFLAGS)
@@ -30,6 +37,16 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+noinst_PROGRAMS += logger_lock_test
+logger_lock_test_SOURCES = logger_lock_test.cc
+nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
+logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
+logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
+logger_lock_test_LDADD = $(top_builddir)/src/lib/log/liblog.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+logger_lock_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
if HAVE_GTEST
TESTS =
@@ -62,6 +79,7 @@ run_unittests_SOURCES += logger_specification_unittest.cc
run_unittests_SOURCES += message_dictionary_unittest.cc
run_unittests_SOURCES += message_reader_unittest.cc
run_unittests_SOURCES += output_option_unittest.cc
+nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
@@ -104,4 +122,5 @@ check-local:
$(SHELL) $(abs_builddir)/destination_test.sh
$(SHELL) $(abs_builddir)/init_logger_test.sh
$(SHELL) $(abs_builddir)/local_file_test.sh
+ $(SHELL) $(abs_builddir)/logger_lock_test.sh
$(SHELL) $(abs_builddir)/severity_test.sh
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/log/tests/log_test_messages.mes b/src/lib/log/tests/log_test_messages.mes
new file mode 100644
index 0000000..ed4940c
--- /dev/null
+++ b/src/lib/log/tests/log_test_messages.mes
@@ -0,0 +1,26 @@
+# 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.
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and
+# logging components. The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+$NAMESPACE isc::log
+
+% LOG_LOCK_TEST_MESSAGE this is a test message.
+This is a log message used in testing.
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
index d3f08f3..853d48a 100644
--- a/src/lib/log/tests/logger_example.cc
+++ b/src/lib/log/tests/logger_example.cc
@@ -41,6 +41,7 @@
// Include a set of message definitions.
#include <log/log_messages.h>
+#include "util/interprocess_sync_null.h"
using namespace isc::log;
using namespace std;
@@ -280,10 +281,17 @@ int main(int argc, char** argv) {
LoggerManager::readLocalMessageFile(argv[optind]);
}
- // Log a few messages to different loggers.
+ // Log a few messages to different loggers. Here, we switch to using
+ // null interprocess sync objects for the loggers below as the
+ // logger example can be used as a standalone program (which may not
+ // have write access to a local state directory to create
+ // lockfiles).
isc::log::Logger logger_ex(ROOT_NAME);
+ logger_ex.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
isc::log::Logger logger_alpha("alpha");
+ logger_alpha.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
isc::log::Logger logger_beta("beta");
+ logger_beta.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
LOG_FATAL(logger_ex, LOG_WRITE_ERROR).arg("test1").arg("42");
LOG_ERROR(logger_ex, LOG_READING_LOCAL_FILE).arg("dummy/file");
diff --git a/src/lib/log/tests/logger_lock_test.cc b/src/lib/log/tests/logger_lock_test.cc
new file mode 100644
index 0000000..d63989c
--- /dev/null
+++ b/src/lib/log/tests/logger_lock_test.cc
@@ -0,0 +1,64 @@
+// 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.
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include "util/interprocess_sync.h"
+#include "log_test_messages.h"
+#include <iostream>
+
+using namespace std;
+using namespace isc::log;
+
+class MockLoggingSync : public isc::util::InterprocessSync {
+public:
+ /// \brief Constructor
+ MockLoggingSync(const std::string& component_name) :
+ InterprocessSync(component_name)
+ {}
+
+protected:
+ virtual bool lock() {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: LOCK\n";
+ return (true);
+ }
+
+ virtual bool tryLock() {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: TRYLOCK\n";
+ return (true);
+ }
+
+ virtual bool unlock() {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: UNLOCK\n";
+ return (true);
+ }
+};
+
+/// \brief Test logger lock sequence
+///
+/// A program used in testing the logger. It verifies that (1) an
+/// interprocess sync lock is first acquired by the logger, (2) the
+/// message is logged by the logger, and (3) the lock is released in
+/// that sequence.
+int
+main(int, char**) {
+ initLogger();
+ Logger logger("log");
+ logger.setInterprocessSync(new MockLoggingSync("log"));
+
+ LOG_INFO(logger, LOG_LOCK_TEST_MESSAGE);
+
+ return (0);
+}
diff --git a/src/lib/log/tests/logger_lock_test.sh.in b/src/lib/log/tests/logger_lock_test.sh.in
new file mode 100755
index 0000000..0324499
--- /dev/null
+++ b/src/lib/log/tests/logger_lock_test.sh.in
@@ -0,0 +1,46 @@
+#!/bin/sh
+# 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.
+
+# Checks that the locker interprocess sync locks are acquired and
+# released correctly.
+
+failcount=0
+tempfile=@abs_builddir@/logger_lock_test_tempfile_$$
+destfile=@abs_builddir@/logger_lock_test_destfile_$$
+
+passfail() {
+ if [ $1 -eq 0 ]; then
+ echo " pass"
+ else
+ echo " FAIL"
+ failcount=`expr $failcount + $1`
+ fi
+}
+
+echo -n "Testing that logger acquires and releases locks correctly:"
+cat > $tempfile << .
+LOGGER_LOCK_TEST: LOCK
+INFO [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
+LOGGER_LOCK_TEST: UNLOCK
+.
+rm -f $destfile
+B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test > $destfile
+cut -d' ' -f3- $destfile | diff $tempfile -
+passfail $?
+
+# Tidy up.
+rm -f $tempfile $destfile
+
+exit $failcount
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
index 069205e..a9330a9 100644
--- a/src/lib/log/tests/logger_unittest.cc
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -23,6 +23,9 @@
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <log/log_messages.h>
+#include "log/tests/log_test_messages.h"
+
+#include <util/interprocess_sync_file.h>
using namespace isc;
using namespace isc::log;
@@ -379,3 +382,66 @@ TEST_F(LoggerTest, LoggerNameLength) {
}, ".*");
#endif
}
+
+TEST_F(LoggerTest, setInterprocessSync) {
+ // Create a logger
+ Logger logger("alpha");
+
+ EXPECT_THROW(logger.setInterprocessSync(NULL), BadInterprocessSync);
+}
+
+class MockSync : public isc::util::InterprocessSync {
+public:
+ /// \brief Constructor
+ MockSync(const std::string& component_name) :
+ InterprocessSync(component_name), was_locked_(false),
+ was_unlocked_(false)
+ {}
+
+ bool wasLocked() const {
+ return (was_locked_);
+ }
+
+ bool wasUnlocked() const {
+ return (was_unlocked_);
+ }
+
+protected:
+ bool lock() {
+ was_locked_ = true;
+ return (true);
+ }
+
+ bool tryLock() {
+ return (true);
+ }
+
+ bool unlock() {
+ was_unlocked_ = true;
+ return (true);
+ }
+
+private:
+ bool was_locked_;
+ bool was_unlocked_;
+};
+
+// Checks that the logger logs exclusively and other BIND 10 components
+// are locked out.
+
+TEST_F(LoggerTest, Lock) {
+ // Create a logger
+ Logger logger("alpha");
+
+ // Setup our own mock sync object so that we can intercept the lock
+ // call and check if a lock has been taken.
+ MockSync* sync = new MockSync("logger");
+ logger.setInterprocessSync(sync);
+
+ // Log a message and put things into play.
+ logger.setSeverity(isc::log::INFO, 100);
+ logger.info(LOG_LOCK_TEST_MESSAGE);
+
+ EXPECT_TRUE(sync->wasLocked());
+ EXPECT_TRUE(sync->wasUnlocked());
+}
diff --git a/src/lib/log/tests/run_initializer_unittests.cc b/src/lib/log/tests/run_initializer_unittests.cc
index 54ee120..6660215 100644
--- a/src/lib/log/tests/run_initializer_unittests.cc
+++ b/src/lib/log/tests/run_initializer_unittests.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <stdlib.h>
#include <gtest/gtest.h>
#include <util/unittests/run_all.h>
diff --git a/src/lib/log/tests/run_unittests.cc b/src/lib/log/tests/run_unittests.cc
index 8a9d1e5..019a548 100644
--- a/src/lib/log/tests/run_unittests.cc
+++ b/src/lib/log/tests/run_unittests.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <stdlib.h>
#include <gtest/gtest.h>
#include <util/unittests/run_all.h>
diff --git a/src/lib/python/isc/bind10/tests/Makefile.am b/src/lib/python/isc/bind10/tests/Makefile.am
index 658db1e..196a8b9 100644
--- a/src/lib/python/isc/bind10/tests/Makefile.am
+++ b/src/lib/python/isc/bind10/tests/Makefile.am
@@ -23,6 +23,7 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
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/config/tests/Makefile.am b/src/lib/python/isc/config/tests/Makefile.am
index 6670ee7..cb59e6f 100644
--- a/src/lib/python/isc/config/tests/Makefile.am
+++ b/src/lib/python/isc/config/tests/Makefile.am
@@ -22,6 +22,7 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/config \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
CONFIG_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/config/tests/testdata \
CONFIG_WR_TESTDATA_PATH=$(abs_top_builddir)/src/lib/config/tests/testdata \
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 6512c19..7e34e70 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -15,6 +15,17 @@
# No namespace declaration - these constants go in the global namespace
# of the libddns_messages python module.
+% LIBDDNS_DATASRC_ERROR update client %1 failed due to data source error: %2
+An update attempt failed due to some error in the corresponding data
+source. This is generally an unexpected event, but can still happen
+for various reasons such as DB lock contention or a failure of the
+backend DB server. The cause of the error is also logged. It's
+advisable to check the message, and, if necessary, take an appropriate
+action (e.g., restarting the DB server if it dies). If this message
+is logged the data source isn't modified due to the
+corresponding update request. When used by the b10-ddns, the server
+will return a response with an RCODE of SERVFAIL.
+
% 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.
@@ -100,6 +111,10 @@ record has an RRType that is considered a 'meta' type, which
cannot be zone content data. The specific record is shown.
A FORMERR response is sent back to the client.
+% 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_BAD_CLASS update client %1 for zone %2: bad class in update RR: %3
The Update section of a DDNS update message contains an RRset with
a bad class. The class of the update RRset must be either the same
@@ -109,8 +124,7 @@ A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
An error occured while committing the DDNS update changes to the
datasource. The specific error is printed. A SERVFAIL response is sent
-back to the client. In theory, if the datasource is implemented
-correctly, no changes should have been made to the zone contents.
+back to the client.
% LIBDDNS_UPDATE_DELETE_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
The Update section of a DDNS update message contains a statement
@@ -126,7 +140,7 @@ A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY update client %1 for zone %2: update deletion RR contains data %3
The Update section of a DDNS update message contains a 'delete rrset'
-statement with a non-empty Rrset. This is not allowed by the protocol.
+statement with a non-empty RRset. This is not allowed by the protocol.
A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE update client %1 for zone %2: update deletion RR bad type: %3
@@ -141,6 +155,18 @@ The Update section of a DDNS update message contains a 'delete rrs'
statement with a non-zero TTL. This is not allowed by the protocol.
A FORMERR response is sent back to the client.
+% 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
@@ -157,14 +183,14 @@ 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 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.
+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_NOTZONE update client %1 for zone %2: update RR out of zone %3
A DDNS UPDATE record has a name that does not appear to be inside
@@ -179,7 +205,7 @@ 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.
-% LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION update client %1 for zone %2: uncaught exception while processing update section: %1
+% LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION update client %1 for zone %2: uncaught exception while processing update section: %3
An uncaught exception was encountered while processing the Update
section of a DDNS message. The specific exception is shown in the log message.
To make sure DDNS service is not interrupted, this problem is caught instead
diff --git a/src/lib/python/isc/ddns/logger.py b/src/lib/python/isc/ddns/logger.py
index be163a8..0f95bd7 100644
--- a/src/lib/python/isc/ddns/logger.py
+++ b/src/lib/python/isc/ddns/logger.py
@@ -28,9 +28,11 @@ class ClientFormatter:
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>'.
+ 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>'.
+ 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
@@ -45,16 +47,23 @@ class ClientFormatter:
Right now this is an open issue.
"""
- def __init__(self, addr):
+ def __init__(self, addr, tsig_record=None):
self.__addr = addr
+ self.__tsig_record = tsig_record
- def __str__(self):
+ 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.
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index e983810..d36f6fd 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -21,6 +21,7 @@ from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter,\
from isc.log_messages.libddns_messages import *
from isc.datasrc import ZoneFinder
import isc.xfrin.diff
+from isc.acl.acl import ACCEPT, REJECT, DROP
import copy
# Result codes for UpdateSession.handle()
@@ -48,7 +49,8 @@ class UpdateError(Exception):
- 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) The RCODE to be set in the response message.
+ - 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.
'''
@@ -59,40 +61,23 @@ class UpdateError(Exception):
self.rcode = rcode
self.nolog = nolog
-def foreach_rr_in_rrset(rrset, method, *kwargs):
- '''Helper function. For DDNS, in a number of cases, we need to
- treat the various RRs in a single RRset separately.
- Our libdns++ has no concept of RRs, so in that case,
- what we do is create a temporary 1-RR RRset for each Rdata
- in the RRset object.
- This method then calls the given method with the given args
- for each of the temporary rrsets (the rrset in *wargs is
- replaced by the temporary one)
- Note: if this method is useful in more places, we may want
- to move it out of ddns.
- Example:
- Say you have a method that prints a prexif string and an
- rrset, def my_print(prefix, rrset)
- Given an rrset my_rrset, you'd print the entire rrset
- with my_print("foo", rrset)
- And with this helper function, to print each rr invidually,
- you'd call
- foreach_rr_in_rrsetet(rrset, my_print, "foo", rrset)
- Note the rrset is needed twice, the first to identify it,
- the second as the 'real' argument to my_print (which is replaced
- by this function.
+def foreach_rr(rrset):
+ '''
+ Generator that creates a new RRset with one RR from
+ the given RRset upon each iteration, usable in calls that
+ need to loop over an RRset and perform an action with each
+ of the individual RRs in it.
+ Example:
+ for rr in foreach_rr(rrset):
+ print(str(rr))
'''
- result = None
for rdata in rrset.get_rdata():
- tmp_rrset = isc.dns.RRset(rrset.get_name(),
- rrset.get_class(),
- rrset.get_type(),
- rrset.get_ttl())
- tmp_rrset.add_rdata(rdata)
- # Replace the rrset in the original arguments by our rrset
- args = [arg if arg != rrset else tmp_rrset for arg in kwargs]
- result = method(*args)
- return result
+ rr = isc.dns.RRset(rrset.get_name(),
+ rrset.get_class(),
+ rrset.get_type(),
+ rrset.get_ttl())
+ rr.add_rdata(rdata)
+ yield rr
def convert_rrset_class(rrset, rrclass):
'''Returns a (new) rrset with the data from the given rrset,
@@ -112,6 +97,34 @@ def convert_rrset_class(rrset, rrclass):
new_rrset.add_rdata(isc.dns.Rdata(rrset.get_type(), rrclass, wire))
return new_rrset
+def collect_rrsets(collection, rrset):
+ '''
+ Helper function to collect similar rrsets.
+ Collect all rrsets with the same name, class, and type
+ collection is the currently collected list of RRsets,
+ rrset is the RRset to add;
+ if an RRset with the same name, class and type as the
+ given rrset exists in the collection, its rdata fields
+ are added to that RRset. Otherwise, the rrset is added
+ to the given collection.
+ TTL is ignored.
+ This method does not check rdata contents for duplicate
+ values.
+
+ The collection and its rrsets are modified in-place,
+ this method does not return anything.
+ '''
+ found = False
+ for existing_rrset in collection:
+ if existing_rrset.get_name() == rrset.get_name() and\
+ existing_rrset.get_class() == rrset.get_class() and\
+ existing_rrset.get_type() == rrset.get_type():
+ for rdata in rrset.get_rdata():
+ existing_rrset.add_rdata(rdata)
+ found = True
+ if not found:
+ collection.append(rrset)
+
class DDNS_SOA:
'''Class to handle the SOA in the DNS update '''
@@ -174,30 +187,24 @@ class UpdateSession:
class can use the message to send a response to the client.
'''
- def __init__(self, req_message, req_data, client_addr, zone_config):
+ def __init__(self, req_message, client_addr, zone_config):
'''Constructor.
- Note: req_data is not really used as of #1512 but is listed since
- it's quite likely we need it in a subsequent task soon. We'll
- also need to get other parameters such as ACLs, for which, it's less
- clear in which form we want to get the information, so it's left
- open for now.
-
Parameters:
- req_message (isc.dns.Message) The request message. This must be
- in the PARSE mode.
- - req_data (binary) Wire format data of the request message.
- It will be used for TSIG verification if necessary.
+ 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.
-
- (It'll soon need to be passed ACL in some way, too)
+ - 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
self.__added_soa = None
@@ -206,8 +213,10 @@ class UpdateSession:
'''Return the update message.
After handle() is called, it's generally transformed to the response
- to be returned to the client; otherwise it would be identical to
- the request message passed on construction.
+ 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
@@ -223,7 +232,8 @@ class UpdateSession:
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().
+ 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
@@ -231,13 +241,13 @@ class UpdateSession:
'''
try:
- self.__get_update_zone()
- # conceptual code that would follow
+ self._get_update_zone()
+ self._create_diff()
prereq_result = self.__check_prerequisites()
if prereq_result != Rcode.NOERROR():
self.__make_response(prereq_result)
return UPDATE_ERROR, self.__zname, self.__zclass
- # self.__check_update_acl()
+ self.__check_update_acl(self.__zname, self.__zclass)
update_result = self.__do_update()
if update_result != Rcode.NOERROR():
self.__make_response(update_result)
@@ -247,12 +257,22 @@ class UpdateSession:
except UpdateError as e:
if not e.nolog:
logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
- ClientFormatter(self.__client_addr),
+ ClientFormatter(self.__client_addr, self.__tsig),
ZoneFormatter(e.zname, e.zclass), e)
- self.__make_response(e.rcode)
+ # 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
+ except isc.datasrc.Error as e:
+ logger.error(LIBDDNS_DATASRC_ERROR,
+ ClientFormatter(self.__client_addr, self.__tsig), e)
+ self.__make_response(Rcode.SERVFAIL())
return UPDATE_ERROR, None, None
- def __get_update_zone(self):
+ 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
@@ -261,8 +281,11 @@ class UpdateSession:
zone
__zname: The zone name as a Name object
__zclass: The zone class as an RRClass object
- __finder: A ZoneFinder for this zone
- If this method raises an exception, these members are not set
+ If this method raises an exception, these members are not set.
+
+ Note: This method is protected for ease of use in tests, where
+ methods are tested that need the setup done here without calling
+ the full handle() method.
'''
# Validation: the zone section must contain exactly one question,
# and it must be of type SOA.
@@ -280,24 +303,62 @@ class UpdateSession:
zclass = zrecord.get_class()
zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
+ self.__datasrc_client = datasrc_client
self.__zname = zname
self.__zclass = zclass
- self.__datasrc_client = datasrc_client
- _, self.__finder = datasrc_client.find_zone(zname)
return
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),
+ 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),
+ ClientFormatter(self.__client_addr, self.__tsig),
ZoneFormatter(zname, zclass))
raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
+ def _create_diff(self):
+ '''
+ Initializes the internal data structure used for searching current
+ data and for adding and deleting data. This is supposed to be called
+ after ACL checks but before prerequisite checks (since the latter
+ needs the find calls provided by the Diff class).
+ Adds the private member:
+ __diff: A buffer of changes made against the zone by this update
+ This object also contains find() calls, see documentation
+ of the Diff class.
+
+ Note: This method is protected for ease of use in tests, where
+ methods are tested that need the setup done here without calling
+ the full handle() method.
+ '''
+ self.__diff = isc.xfrin.diff.Diff(self.__datasrc_client,
+ self.__zname,
+ journaling=True,
+ single_update_mode=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.
@@ -322,9 +383,7 @@ class UpdateSession:
only return what the result code would be (and not read/copy
any actual data).
'''
- result, _, _ = self.__finder.find(rrset.get_name(), rrset.get_type(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
+ result, _, _ = self.__diff.find(rrset.get_name(), rrset.get_type())
return result == ZoneFinder.SUCCESS
def __prereq_rrset_exists_value(self, rrset):
@@ -333,10 +392,8 @@ class UpdateSession:
RFC2136 Section 2.4.2
Returns True if the prerequisite is satisfied, False otherwise.
'''
- result, found_rrset, _ = self.__finder.find(rrset.get_name(),
- rrset.get_type(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
+ result, found_rrset, _ = self.__diff.find(rrset.get_name(),
+ rrset.get_type())
if result == ZoneFinder.SUCCESS and\
rrset.get_name() == found_rrset.get_name() and\
rrset.get_type() == found_rrset.get_type():
@@ -375,9 +432,7 @@ class UpdateSession:
to only return what the result code would be (and not read/copy
any actual data).
'''
- result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
+ result, rrsets, flags = self.__diff.find_all(rrset.get_name())
if result == ZoneFinder.SUCCESS and\
(flags & ZoneFinder.RESULT_WILDCARD == 0):
return True
@@ -404,6 +459,10 @@ class UpdateSession:
Returns a dns Rcode signaling either no error (Rcode.NOERROR())
or that one of the prerequisites failed (any other Rcode).
'''
+
+ # Temporary array to store exact-match RRsets
+ exact_match_rrsets = []
+
for rrset in self.__message.get_section(SECTION_PREREQUISITE):
# First check if the name is in the zone
if not self.__check_in_zone(rrset):
@@ -470,13 +529,7 @@ class UpdateSession:
RRsetFormatter(rrset))
return Rcode.FORMERR()
else:
- if not self.__prereq_rrset_exists_value(rrset):
- rcode = Rcode.NXRRSET()
- logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
- ClientFormatter(self.__client_addr),
- ZoneFormatter(self.__zname, self.__zclass),
- RRsetFormatter(rrset), rcode)
- return rcode
+ collect_rrsets(exact_match_rrsets, rrset)
else:
logger.info(LIBDDNS_PREREQ_FORMERR_CLASS,
ClientFormatter(self.__client_addr),
@@ -484,6 +537,15 @@ class UpdateSession:
RRsetFormatter(rrset))
return Rcode.FORMERR()
+ for collected_rrset in exact_match_rrsets:
+ if not self.__prereq_rrset_exists_value(collected_rrset):
+ rcode = Rcode.NXRRSET()
+ logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(self.__zname, self.__zclass),
+ RRsetFormatter(collected_rrset), rcode)
+ return rcode
+
# All prerequisites are satisfied
return Rcode.NOERROR()
@@ -520,7 +582,8 @@ class UpdateSession:
if rrset.get_type() == RRType.SOA():
# In case there's multiple soa records in the update
# somehow, just take the last
- foreach_rr_in_rrset(rrset, self.__set_soa_rrset, rrset)
+ for rr in foreach_rr(rrset):
+ self.__set_soa_rrset(rr)
elif rrset.get_class() == RRClass.ANY():
if rrset.get_ttl().get_value() != 0:
logger.info(LIBDDNS_UPDATE_DELETE_NONZERO_TTL,
@@ -562,20 +625,20 @@ class UpdateSession:
return Rcode.FORMERR()
return Rcode.NOERROR()
- def __do_update_add_single_rr(self, diff, rr, existing_rrset):
+ def __do_update_add_single_rr(self, rr, existing_rrset):
'''Helper for __do_update_add_rrs_to_rrset: only add the
rr if it is not present yet
(note that rr here should already be a single-rr rrset)
'''
if existing_rrset is None:
- diff.add_data(rr)
+ self.__diff.add_data(rr)
else:
rr_rdata = rr.get_rdata()[0]
if not rr_rdata in existing_rrset.get_rdata():
- diff.add_data(rr)
+ self.__diff.add_data(rr)
- def __do_update_add_rrs_to_rrset(self, diff, rrset):
- '''Add the rrs from the given rrset to the diff.
+ def __do_update_add_rrs_to_rrset(self, rrset):
+ '''Add the rrs from the given rrset to the internal diff.
There is handling for a number of special cases mentioned
in RFC2136;
- If the addition is a CNAME, but existing data at its
@@ -590,14 +653,12 @@ class UpdateSession:
'''
# For a number of cases, we may need to remove data in the zone
# (note; SOA is handled separately by __do_update, so that one
- # is not explicitely ignored here)
+ # is explicitely ignored here)
if rrset.get_type() == RRType.SOA():
return
- result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
- rrset.get_type(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
- if result == self.__finder.CNAME:
+ result, orig_rrset, _ = self.__diff.find(rrset.get_name(),
+ rrset.get_type())
+ if result == ZoneFinder.CNAME:
# Ignore non-cname rrs that try to update CNAME records
# (if rrset itself is a CNAME, the finder result would be
# SUCCESS, see next case)
@@ -607,7 +668,7 @@ class UpdateSession:
if rrset.get_type() == RRType.CNAME():
# Remove original CNAME record (the new one
# is added below)
- diff.delete_data(orig_rrset)
+ self.__diff.delete_data(orig_rrset)
# We do not have WKS support at this time, but if there
# are special Update equality rules such as for WKS, and
# we do have support for the type, this is where the check
@@ -617,36 +678,41 @@ class UpdateSession:
# If this type is CNAME, ignore the update
if rrset.get_type() == RRType.CNAME():
return
- foreach_rr_in_rrset(rrset, self.__do_update_add_single_rr, diff, rrset, orig_rrset)
+ for rr in foreach_rr(rrset):
+ self.__do_update_add_single_rr(rr, orig_rrset)
- def __do_update_delete_rrset(self, diff, rrset):
+ def __do_update_delete_rrset(self, rrset):
'''Deletes the rrset with the name and type of the given
rrset from the zone data (by putting all existing data
- in the given diff as delete statements).
+ in the internal diff as delete statements).
Special cases: if the delete statement is for the
zone's apex, and the type is either SOA or NS, it
is ignored.'''
- result, to_delete, _ = self.__finder.find(rrset.get_name(),
- rrset.get_type(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
- if to_delete.get_name() == self.__zname and\
- (to_delete.get_type() == RRType.SOA() or\
- to_delete.get_type() == RRType.NS()):
- # ignore
- return
- foreach_rr_in_rrset(to_delete, diff.delete_data, to_delete)
+ result, to_delete, _ = self.__diff.find(rrset.get_name(),
+ rrset.get_type())
+ if result == ZoneFinder.SUCCESS:
+ if to_delete.get_name() == self.__zname and\
+ (to_delete.get_type() == RRType.SOA() or\
+ to_delete.get_type() == RRType.NS()):
+ # ignore
+ return
+ for rr in foreach_rr(to_delete):
+ self.__diff.delete_data(rr)
- def __ns_deleter_helper(self, diff, rrset):
+ def __ns_deleter_helper(self, rrset):
'''Special case helper for deleting NS resource records
at the zone apex. In that scenario, the last NS record
may never be removed (and any action that would do so
should be ignored).
'''
- result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
- rrset.get_type(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
+ # NOTE: This method is currently bad: it WILL delete all
+ # NS rrsets in a number of cases.
+ # We need an extension to our diff.py to handle this correctly
+ # (see ticket #2016)
+ # The related test is currently disabled. When this is fixed,
+ # enable that test again.
+ result, orig_rrset, _ = self.__diff.find(rrset.get_name(),
+ rrset.get_type())
# Even a real rrset comparison wouldn't help here...
# The goal is to make sure that after deletion of the
# given rrset, at least 1 NS record is left (at the apex).
@@ -667,18 +733,16 @@ class UpdateSession:
rrset.get_ttl())
to_delete.add_rdata(rdata)
orig_rrset_rdata.remove(rdata)
- diff.delete_data(to_delete)
+ self.__diff.delete_data(to_delete)
- def __do_update_delete_name(self, diff, rrset):
+ def __do_update_delete_name(self, rrset):
'''Delete all data at the name of the given rrset,
by adding all data found by find_all as delete statements
- to the given diff.
+ to the internal diff.
Special case: if the name is the zone's apex, SOA and
NS records are kept.
'''
- result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
- ZoneFinder.NO_WILDCARD |
- ZoneFinder.FIND_GLUE_OK)
+ result, rrsets, flags = self.__diff.find_all(rrset.get_name())
if result == ZoneFinder.SUCCESS and\
(flags & ZoneFinder.RESULT_WILDCARD == 0):
for to_delete in rrsets:
@@ -688,9 +752,10 @@ class UpdateSession:
to_delete.get_type() == RRType.NS()):
continue
else:
- foreach_rr_in_rrset(to_delete, diff.delete_data, to_delete)
+ for rr in foreach_rr(to_delete):
+ self.__diff.delete_data(rr)
- def __do_update_delete_rrs_from_rrset(self, diff, rrset):
+ def __do_update_delete_rrs_from_rrset(self, rrset):
'''Deletes all resource records in the given rrset from the
zone. Resource records that do not exist are ignored.
If the rrset if of type SOA, it is ignored.
@@ -711,16 +776,17 @@ class UpdateSession:
elif rrset.get_type() == RRType.NS():
# hmm. okay. annoying. There must be at least one left,
# delegate to helper method
- self.__ns_deleter_helper(diff, to_delete)
+ self.__ns_deleter_helper(to_delete)
return
- foreach_rr_in_rrset(to_delete, diff.delete_data, to_delete)
+ for rr in foreach_rr(to_delete):
+ self.__diff.delete_data(rr)
- def __update_soa(self, diff):
+ def __update_soa(self):
'''Checks the member value __added_soa, and depending on
whether it has been set and what its value is, creates
a new SOA if necessary.
Then removes the original SOA and adds the new one,
- by adding the needed operations to the given diff.'''
+ by adding the needed operations to the internal diff.'''
# Get the existing SOA
# if a new soa was specified, add that one, otherwise, do the
# serial magic and add the newly created one
@@ -742,8 +808,8 @@ class UpdateSession:
# increment goes here
new_soa = serial_operation.update_soa(old_soa)
- diff.delete_data(old_soa)
- diff.add_data(new_soa)
+ self.__diff.delete_data(old_soa)
+ self.__diff.add_data(new_soa)
def __do_update(self):
'''Scan, check, and execute the Update section in the
@@ -758,12 +824,8 @@ class UpdateSession:
# update
try:
- # create an ixfr-out-friendly diff structure to work on
- diff = isc.xfrin.diff.Diff(self.__datasrc_client, self.__zname,
- journaling=True, single_update_mode=True)
-
# Do special handling for SOA first
- self.__update_soa(diff)
+ self.__update_soa()
# Algorithm from RFC2136 Section 3.4
# Note that this works on full rrsets, not individual RRs.
@@ -773,20 +835,20 @@ class UpdateSession:
# of the Diff class, this is not the case, and therefore it
# is easier to work with full rrsets for the most parts
# (less lookups needed; conversion to individual rrs is
- # the same offort whether it is done here or in the several
+ # the same effort whether it is done here or in the several
# do_update statements)
for rrset in self.__message.get_section(SECTION_UPDATE):
if rrset.get_class() == self.__zclass:
- self.__do_update_add_rrs_to_rrset(diff, rrset)
+ self.__do_update_add_rrs_to_rrset(rrset)
elif rrset.get_class() == RRClass.ANY():
if rrset.get_type() == RRType.ANY():
- self.__do_update_delete_name(diff, rrset)
+ self.__do_update_delete_name(rrset)
else:
- self.__do_update_delete_rrset(diff, rrset)
+ self.__do_update_delete_rrset(rrset)
elif rrset.get_class() == RRClass.NONE():
- self.__do_update_delete_rrs_from_rrset(diff, rrset)
+ self.__do_update_delete_rrs_from_rrset(rrset)
- diff.commit()
+ self.__diff.commit()
return Rcode.NOERROR()
except isc.datasrc.Error as dse:
logger.info(LIBDDNS_UPDATE_DATASRC_ERROR, dse)
diff --git a/src/lib/python/isc/ddns/tests/Makefile.am b/src/lib/python/isc/ddns/tests/Makefile.am
index f8e05b8..4235a2b 100644
--- a/src/lib/python/isc/ddns/tests/Makefile.am
+++ b/src/lib/python/isc/ddns/tests/Makefile.am
@@ -6,7 +6,7 @@ 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/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(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
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index 3349375..f191995 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -35,9 +35,11 @@ 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=[],
- updates=[]):
+ updates=[], tsig_key=None):
msg = Message(Message.RENDER)
msg.set_qid(5353) # arbitrary chosen
msg.set_opcode(Opcode.UPDATE())
@@ -50,13 +52,26 @@ def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[],
msg.add_rrset(SECTION_UPDATE, u)
renderer = MessageRenderer()
- msg.to_wire(renderer)
+ 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())
+ msg.from_wire(renderer.get_data(), Message.PRESERVE_ORDER)
- return renderer.get_data(), msg
+ return msg
+
+def add_rdata(rrset, rdata):
+ '''
+ Helper function for easily adding Rdata fields to RRsets.
+ This function assumes the given rdata is of type string or bytes,
+ and corresponds to the given rrset
+ '''
+ rrset.add_rdata(isc.dns.Rdata(rrset.get_type(),
+ rrset.get_class(),
+ rdata))
def create_rrset(name, rrclass, rrtype, ttl, rdatas = []):
'''
@@ -79,15 +94,135 @@ def create_rrset(name, rrclass, rrtype, ttl, rdatas = []):
add_rdata(rrset, rdata)
return rrset
-def add_rdata(rrset, rdata):
- '''
- Helper function for easily adding Rdata fields to RRsets.
- This function assumes the given rdata is of type string or bytes,
- and corresponds to the given rrset
+class SessionModuleTests(unittest.TestCase):
+ '''Tests for module-level functions in the session.py module'''
+
+ def test_foreach_rr_in_rrset(self):
+ rrset = create_rrset("www.example.org", TEST_RRCLASS,
+ RRType.A(), 3600, [ "192.0.2.1" ])
+
+ l = []
+ for rr in foreach_rr(rrset):
+ l.append(str(rr))
+ self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n"], l)
+
+ add_rdata(rrset, "192.0.2.2")
+ add_rdata(rrset, "192.0.2.3")
+
+ # but through the generator, there should be several 1-line entries
+ l = []
+ for rr in foreach_rr(rrset):
+ l.append(str(rr))
+ self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n",
+ "www.example.org. 3600 IN A 192.0.2.2\n",
+ "www.example.org. 3600 IN A 192.0.2.3\n",
+ ], l)
+
+ def test_convert_rrset_class(self):
+ # Converting an RRSET to a different class should work
+ # if the rdata types can be converted
+ rrset = create_rrset("www.example.org", RRClass.NONE(), RRType.A(),
+ 3600, [ b'\xc0\x00\x02\x01', b'\xc0\x00\x02\x02'])
+
+ rrset2 = convert_rrset_class(rrset, RRClass.IN())
+ self.assertEqual("www.example.org. 3600 IN A 192.0.2.1\n" +
+ "www.example.org. 3600 IN A 192.0.2.2\n",
+ str(rrset2))
+
+ rrset3 = convert_rrset_class(rrset2, RRClass.NONE())
+ self.assertEqual("www.example.org. 3600 CLASS254 A \\# 4 " +
+ "c0000201\nwww.example.org. 3600 CLASS254 " +
+ "A \\# 4 c0000202\n",
+ str(rrset3))
+
+ # depending on what type of bad data is given, a number
+ # of different exceptions could be raised (TODO: i recall
+ # there was a ticket about making a better hierarchy for
+ # dns/parsing related exceptions)
+ self.assertRaises(InvalidRdataLength, convert_rrset_class,
+ rrset, RRClass.CH())
+ add_rdata(rrset, b'\xc0\x00')
+ self.assertRaises(DNSMessageFORMERR, convert_rrset_class,
+ rrset, RRClass.IN())
+
+ def test_collect_rrsets(self):
+ '''
+ Tests the 'rrset collector' method, which collects rrsets
+ with the same name and type
+ '''
+ collected = []
+
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.1" ]))
+ # Same name and class, different type
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.TXT(), 0, [ "one" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.2" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.TXT(), 0, [ "two" ]))
+ # Same class and type as an existing one, different name
+ collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.3" ]))
+ # Same name and type as an existing one, different class
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+ RRType.TXT(), 0, [ "one" ]))
+ collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.4" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+ RRType.TXT(), 0, [ "two" ]))
+
+ strings = [ rrset.to_text() for rrset in collected ]
+ # note + vs , in this list
+ expected = ['a.example.org. 0 IN A 192.0.2.1\n' +
+ 'a.example.org. 0 IN A 192.0.2.2\n',
+ 'a.example.org. 0 IN TXT "one"\n' +
+ 'a.example.org. 0 IN TXT "two"\n',
+ 'b.example.org. 0 IN A 192.0.2.3\n' +
+ 'b.example.org. 0 IN A 192.0.2.4\n',
+ 'a.example.org. 0 CH TXT "one"\n' +
+ 'a.example.org. 0 CH TXT "two"\n']
+
+ self.assertEqual(expected, strings)
+
+class SessionTestBase(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).
+
'''
- rrset.add_rdata(isc.dns.Rdata(rrset.get_type(),
- rrset.get_class(),
- rdata))
+ 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))
+ self._session._get_update_zone()
+ self._session._create_diff()
+
+ def tearDown(self):
+ # With the Updater created in _get_update_zone, and tests
+ # doing all kinds of crazy stuff, one might get database locked
+ # errors if it doesn't clean up explicitely after each test
+ self._session = None
+
+ 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 TestDDNSSOA(unittest.TestCase):
'''unittest for the DDNS_SOA'''
@@ -148,35 +283,12 @@ class TestDDNSSOA(unittest.TestCase):
self.assertFalse(soa_update.soa_update_check(large_soa_rr,
small_soa_rr))
-
-class SessionTest(unittest.TestCase):
- '''Session tests'''
- def setUp(self):
- shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
- self.__datasrc_client = DataSourceClient("sqlite3",
- WRITE_ZONE_DB_CONFIG)
- self.__update_msgdata, self.__update_msg = create_update_msg()
- self.__session = UpdateSession(self.__update_msg,
- self.__update_msgdata, TEST_CLIENT4,
- ZoneConfig([], TEST_RRCLASS,
- self.__datasrc_client))
- self.__session._UpdateSession__get_update_zone()
-
- 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(SessionTestBase):
+ '''Basic session tests'''
def test_handle(self):
'''Basic update case'''
- result, zname, zclass = self.__session.handle()
+ result, zname, zclass = self._session.handle()
self.assertEqual(UPDATE_SUCCESS, result)
self.assertEqual(TEST_ZONE_NAME, zname)
self.assertEqual(TEST_RRCLASS, zclass)
@@ -187,8 +299,8 @@ class SessionTest(unittest.TestCase):
def test_broken_request(self):
# Zone section is empty
- msg_data, msg = create_update_msg(zones=[])
- session = UpdateSession(msg, msg_data, TEST_CLIENT6, None)
+ 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)
@@ -196,17 +308,15 @@ class SessionTest(unittest.TestCase):
self.check_response(session.get_message(), Rcode.FORMERR())
# Zone section contains multiple records
- msg_data, msg = create_update_msg(zones=[TEST_ZONE_RECORD,
- TEST_ZONE_RECORD])
- session = UpdateSession(msg, msg_data, TEST_CLIENT4, None)
+ 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_data, msg = create_update_msg(zones=[Question(TEST_ZONE_NAME,
- TEST_RRCLASS,
- RRType.A())])
- session = UpdateSession(msg, msg_data, TEST_CLIENT4, None)
+ 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())
@@ -214,24 +324,20 @@ class SessionTest(unittest.TestCase):
# specified zone is configured as a secondary. Since this
# implementation doesn't support update forwarding, the result
# should be NOTIMP.
- msg_data, msg = create_update_msg(zones=[Question(TEST_ZONE_NAME,
- TEST_RRCLASS,
- RRType.SOA())])
- session = UpdateSession(msg, msg_data, TEST_CLIENT4,
+ 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))
+ 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_data, msg = create_update_msg(zones=[Question(zname, zclass,
- RRType.SOA())])
- session = UpdateSession(msg, msg_data, TEST_CLIENT4,
+ 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))
+ TEST_RRCLASS, self._datasrc_client))
self.assertEqual(UPDATE_ERROR, session.handle()[0])
self.check_response(session.get_message(), Rcode.NOTAUTH())
@@ -245,33 +351,37 @@ class SessionTest(unittest.TestCase):
# zone class doesn't match
self.check_notauth(Name('example.org'), RRClass.CH())
- def foreach_rr_in_rrset_helper(self, rr, l):
- l.append(rr.to_text())
+ def test_update_datasrc_error(self):
+ # if the data source client raises an exception, it should result in
+ # a SERVFAIL.
+ class BadDataSourceClient:
+ def find_zone(self, name):
+ raise isc.datasrc.Error('faked exception')
+ 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,
+ BadDataSourceClient()))
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.SERVFAIL())
def test_foreach_rr_in_rrset(self):
rrset = create_rrset("www.example.org", TEST_RRCLASS,
RRType.A(), 3600, [ "192.0.2.1" ])
l = []
- foreach_rr_in_rrset(rrset, self.foreach_rr_in_rrset_helper, rrset, l)
+ for rr in foreach_rr(rrset):
+ l.append(str(rr))
self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n"], l)
add_rdata(rrset, "192.0.2.2")
add_rdata(rrset, "192.0.2.3")
- # if the helper is called directly, the list should have
- # one entry, with a multiline string
- # but through the helper, there should be several 1-line entries
+ # but through the generator, there should be several 1-line entries
l = []
- self.foreach_rr_in_rrset_helper(rrset, l)
- self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n" +
- "www.example.org. 3600 IN A 192.0.2.2\n" +
- "www.example.org. 3600 IN A 192.0.2.3\n"
- ], l)
-
- # but through the helper, there should be several 1-line entries
- l = []
- foreach_rr_in_rrset(rrset, self.foreach_rr_in_rrset_helper, rrset, l)
+ for rr in foreach_rr(rrset):
+ l.append(str(rr))
self.assertEqual(["www.example.org. 3600 IN A 192.0.2.1\n",
"www.example.org. 3600 IN A 192.0.2.2\n",
"www.example.org. 3600 IN A 192.0.2.3\n",
@@ -304,8 +414,48 @@ class SessionTest(unittest.TestCase):
self.assertRaises(DNSMessageFORMERR, convert_rrset_class,
rrset, RRClass.IN())
+ def test_collect_rrsets(self):
+ '''
+ Tests the 'rrset collector' method, which collects rrsets
+ with the same name and type
+ '''
+ collected = []
+
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.1" ]))
+ # Same name and class, different type
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.TXT(), 0, [ "one" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.2" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.IN(),
+ RRType.TXT(), 0, [ "two" ]))
+ # Same class and type as an existing one, different name
+ collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.3" ]))
+ # Same name and type as an existing one, different class
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+ RRType.TXT(), 0, [ "one" ]))
+ collect_rrsets(collected, create_rrset("b.example.org", RRClass.IN(),
+ RRType.A(), 0, [ "192.0.2.4" ]))
+ collect_rrsets(collected, create_rrset("a.example.org", RRClass.CH(),
+ RRType.TXT(), 0, [ "two" ]))
+
+ strings = [ rrset.to_text() for rrset in collected ]
+ # note + vs , in this list
+ expected = ['a.example.org. 0 IN A 192.0.2.1\n' +
+ 'a.example.org. 0 IN A 192.0.2.2\n',
+ 'a.example.org. 0 IN TXT "one"\n' +
+ 'a.example.org. 0 IN TXT "two"\n',
+ 'b.example.org. 0 IN A 192.0.2.3\n' +
+ 'b.example.org. 0 IN A 192.0.2.4\n',
+ 'a.example.org. 0 CH TXT "one"\n' +
+ 'a.example.org. 0 CH TXT "two"\n']
+
+ self.assertEqual(expected, strings)
+
def __prereq_helper(self, method, expected, rrset):
- '''Calls the given method with self.__datasrc_client
+ '''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(rrset))
@@ -362,19 +512,19 @@ class SessionTest(unittest.TestCase):
self.__prereq_helper(method, expected, rrset)
def test_check_prerequisite_exists(self):
- method = self.__session._UpdateSession__prereq_rrset_exists
+ method = self._session._UpdateSession__prereq_rrset_exists
self.__check_prerequisite_exists_combined(method,
RRClass.ANY(),
True)
def test_check_prerequisite_does_not_exist(self):
- method = self.__session._UpdateSession__prereq_rrset_does_not_exist
+ method = self._session._UpdateSession__prereq_rrset_does_not_exist
self.__check_prerequisite_exists_combined(method,
RRClass.NONE(),
False)
def test_check_prerequisite_exists_value(self):
- method = self.__session._UpdateSession__prereq_rrset_exists_value
+ method = self._session._UpdateSession__prereq_rrset_exists_value
rrset = create_rrset("www.example.org", RRClass.IN(), RRType.A(), 0)
# empty one should not match
@@ -450,13 +600,13 @@ class SessionTest(unittest.TestCase):
self.__prereq_helper(method, expected, rrset)
def test_check_prerequisite_name_in_use(self):
- method = self.__session._UpdateSession__prereq_name_in_use
+ method = self._session._UpdateSession__prereq_name_in_use
self.__check_prerequisite_name_in_use_combined(method,
RRClass.ANY(),
True)
def test_check_prerequisite_name_not_in_use(self):
- method = self.__session._UpdateSession__prereq_name_not_in_use
+ method = self._session._UpdateSession__prereq_name_not_in_use
self.__check_prerequisite_name_in_use_combined(method,
RRClass.NONE(),
False)
@@ -466,11 +616,12 @@ class SessionTest(unittest.TestCase):
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_data, msg = create_update_msg([TEST_ZONE_RECORD],
- prerequisites)
- zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
- session = UpdateSession(msg, msg_data, TEST_CLIENT4, zconfig)
- session._UpdateSession__get_update_zone()
+ msg = create_update_msg([TEST_ZONE_RECORD], prerequisites)
+ zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ self._acl_map)
+ session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+ session._get_update_zone()
+ session._create_diff()
# 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
@@ -478,8 +629,8 @@ class SessionTest(unittest.TestCase):
session._UpdateSession__check_prerequisites().to_text())
# Now see if handle finds the same result
(result, _, _) = session.handle()
- self.assertEqual(expected,
- session._UpdateSession__message.get_rcode())
+ self.assertEqual(expected.to_text(),
+ session._UpdateSession__message.get_rcode().to_text())
# And that the result looks right
if expected == Rcode.NOERROR():
self.assertEqual(UPDATE_SUCCESS, result)
@@ -491,11 +642,12 @@ class SessionTest(unittest.TestCase):
creates an update session, and fills it with the list of rrsets
from 'updates'. Then checks if __do_prescan()
returns the Rcode specified in 'expected'.'''
- msg_data, msg = create_update_msg([TEST_ZONE_RECORD],
- [], updates)
- zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
- session = UpdateSession(msg, msg_data, TEST_CLIENT4, zconfig)
- session._UpdateSession__get_update_zone()
+ msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
+ zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ self._acl_map)
+ session = UpdateSession(msg, TEST_CLIENT4, zconfig)
+ session._get_update_zone()
+ session._create_diff()
# 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
@@ -510,10 +662,10 @@ class SessionTest(unittest.TestCase):
creates an update session, and fills it with the list of rrsets
from 'updates'. Then checks if __handle()
results in a response with rcode 'expected'.'''
- msg_data, msg = create_update_msg([TEST_ZONE_RECORD],
- [], updates)
- zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
- session = UpdateSession(msg, msg_data, TEST_CLIENT4, zconfig)
+ msg = create_update_msg([TEST_ZONE_RECORD], [], updates)
+ zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client,
+ self._acl_map)
+ session = UpdateSession(msg, TEST_CLIENT4, zconfig)
# Now see if handle finds the same result
(result, _, _) = session.handle()
@@ -575,16 +727,6 @@ class SessionTest(unittest.TestCase):
name_not_in_use_no = create_rrset("www.example.org", RRClass.NONE(),
RRType.ANY(), 0)
-
- # Create an UPDATE with all 5 'yes' prereqs
- data, update = 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 ])
@@ -598,6 +740,23 @@ class SessionTest(unittest.TestCase):
[ name_not_in_use_no ])
# the 'yes' codes should result in ok
+ # individually
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_exists_yes ] )
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_exists_value_yes ])
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_does_not_exist_yes ])
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ name_in_use_yes ])
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ name_not_in_use_yes ])
+ self.check_prerequisite_result(Rcode.NOERROR(),
+ [ rrset_exists_value_1,
+ rrset_exists_value_2,
+ rrset_exists_value_3])
+
+ # and together
self.check_prerequisite_result(Rcode.NOERROR(),
[ rrset_exists_yes,
rrset_exists_value_yes,
@@ -662,14 +821,13 @@ class SessionTest(unittest.TestCase):
[ "foo" ])
self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ])
-
def __prereq_helper(self, method, expected, rrset):
- '''Calls the given method with self.__datasrc_client
+ '''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(rrset))
- def initialize_update_rrsets(self):
+ def __initialize_update_rrsets(self):
'''Prepare a number of RRsets to be used in several update tests
The rrsets are stored in self'''
orig_a_rrset = create_rrset("www.example.org", TEST_RRCLASS,
@@ -750,7 +908,7 @@ class SessionTest(unittest.TestCase):
'''Test whether the prescan succeeds on data that is ok, and whether
if notices the SOA if present'''
# prepare a set of correct update statements
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
self.check_prescan_result(Rcode.NOERROR(), [ self.rrset_update_a ])
@@ -845,13 +1003,13 @@ class SessionTest(unittest.TestCase):
[ b'\x00' ])
self.check_prescan_result(Rcode.FORMERR(), [ rrset ])
- def check_inzone_data(self, expected_result, name, rrtype,
- expected_rrset = None):
+ def __check_inzone_data(self, expected_result, name, rrtype,
+ expected_rrset = None):
'''Does a find on TEST_ZONE for the given rrset's name and type,
then checks if the result matches the expected result.
If so, and if expected_rrset is given, they are compared as
well.'''
- _, finder = self.__datasrc_client.find_zone(TEST_ZONE_NAME)
+ _, finder = self._datasrc_client.find_zone(TEST_ZONE_NAME)
result, found_rrset, _ = finder.find(name, rrtype,
finder.NO_WILDCARD |
finder.FIND_GLUE_OK)
@@ -877,7 +1035,7 @@ class SessionTest(unittest.TestCase):
Tests a sequence of related add and delete updates. Some other
cases are tested by later tests.
'''
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
# initially, the www should only contain one rr
# (set to self.orig_a_rrset)
@@ -890,115 +1048,191 @@ class SessionTest(unittest.TestCase):
"192.0.2.3" ])
# Sanity check, make sure original data is really there before updates
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- self.orig_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ self.orig_a_rrset)
# Add two rrs
self.check_full_handle_result(Rcode.NOERROR(), [ self.rrset_update_a ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- extended_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ extended_a_rrset)
# Adding the same RRsets should not make a difference.
self.check_full_handle_result(Rcode.NOERROR(), [ self.rrset_update_a ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- extended_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ extended_a_rrset)
# Now delete those two, and we should end up with the original RRset
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_rrset_part ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- self.orig_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ self.orig_a_rrset)
# 'Deleting' them again should make no difference
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_rrset_part ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- self.orig_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ self.orig_a_rrset)
# But deleting the entire rrset, independent of its contents, should
# work
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
- isc.dns.Name("www.example.org"),
- RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("www.example.org"),
+ RRType.A())
# Check that if we update the SOA, it is updated to our value
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_soa2 ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.SOA(),
- self.rrset_update_soa2)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.SOA(),
+ self.rrset_update_soa2)
+
+ def test_glue_deletions(self):
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("sub.example.org."),
+ RRType.NS())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("ns.sub.example.org."),
+ RRType.A())
+
+ # See that we can delete glue
+ rrset_delete_glue = create_rrset("ns.sub.example.org.",
+ RRClass.ANY(),
+ RRType.A(),
+ 0)
+ self.check_full_handle_result(Rcode.NOERROR(),
+ [ rrset_delete_glue ])
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("sub.example.org."),
+ RRType.NS())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("ns.sub.example.org."),
+ RRType.A())
+
+ # Check that we don't accidentally delete a delegation if we
+ # try to delete non-existent glue
+ rrset_delete_nonexistent_glue = create_rrset("foo.sub.example.org.",
+ RRClass.ANY(),
+ RRType.A(),
+ 0)
+ self.check_full_handle_result(Rcode.NOERROR(),
+ [ rrset_delete_nonexistent_glue ])
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("sub.example.org."),
+ RRType.NS())
def test_update_add_new_data(self):
'''
This tests adds data where none is present
'''
# Add data at a completely new name
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
- isc.dns.Name("new.example.org"),
- RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("new.example.org"),
+ RRType.A())
rrset = create_rrset("new.example.org", TEST_RRCLASS, RRType.A(),
3600, [ "192.0.2.1", "192.0.2.2" ])
self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("new.example.org"),
- RRType.A(),
- rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("new.example.org"),
+ RRType.A(),
+ rrset)
# Also try a name where data is present, but none of this
# specific type
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
- isc.dns.Name("new.example.org"),
- RRType.TXT())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+ isc.dns.Name("new.example.org"),
+ RRType.TXT())
rrset = create_rrset("new.example.org", TEST_RRCLASS, RRType.TXT(),
3600, [ "foo" ])
self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("new.example.org"),
- RRType.TXT(),
- rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("new.example.org"),
+ RRType.TXT(),
+ rrset)
+
+ def test_update_add_new_data_interspersed(self):
+ '''
+ This tests adds data where none is present, similar to
+ test_update_add_new_data, but this time the second RRset
+ is put into the record between the two RRs of the first
+ RRset.
+ '''
+ # Add data at a completely new name
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("new_a.example.org"),
+ RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("new_txt.example.org"),
+ RRType.TXT())
+
+ rrset1 = create_rrset("new_a.example.org", TEST_RRCLASS, RRType.A(),
+ 3600, [ "192.0.2.1" ])
+
+ rrset2 = create_rrset("new_txt.example.org", TEST_RRCLASS, RRType.TXT(),
+ 3600, [ "foo" ])
+
+ rrset3 = create_rrset("new_a.example.org", TEST_RRCLASS, RRType.A(),
+ 3600, [ "192.0.2.2" ])
+
+ self.check_full_handle_result(Rcode.NOERROR(),
+ [ rrset1, rrset2, rrset3 ])
+
+ # The update should have merged rrset1 and rrset3
+ rrset_merged = create_rrset("new_a.example.org", TEST_RRCLASS,
+ RRType.A(), 3600,
+ [ "192.0.2.1", "192.0.2.2" ])
+
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("new_a.example.org"),
+ RRType.A(),
+ rrset_merged)
+
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("new_txt.example.org"),
+ RRType.TXT(),
+ rrset2)
def test_update_delete_name(self):
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
# First check it is there
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A())
# Delete the entire name
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_name ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
- isc.dns.Name("www.example.org"),
- RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("www.example.org"),
+ RRType.A())
# Should still be gone after pointless second delete
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_name ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
- isc.dns.Name("www.example.org"),
- RRType.A())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXDOMAIN,
+ isc.dns.Name("www.example.org"),
+ RRType.A())
def test_update_apex_special_cases(self):
'''
Tests a few special cases when deleting data from the apex
'''
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
# the original SOA
orig_soa_rrset = create_rrset("example.org", TEST_RRCLASS,
@@ -1023,21 +1257,16 @@ class SessionTest(unittest.TestCase):
"ns2.example.org.",
"ns3.example.org." ])
- # When we are done, we should have a reduced NS rrset
- short_ns_rrset = create_rrset("example.org", TEST_RRCLASS,
- RRType.NS(), 3600,
- [ "ns3.example.org." ])
-
# Sanity check, make sure original data is really there before updates
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.NS(),
- orig_ns_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.NS(),
+ orig_ns_rrset)
# We will delete the MX record later in this test, so let's make
# sure that it exists (we do not care about its value)
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.MX())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.MX())
# Check that we cannot delete the SOA record by direction deletion
# both by name+type and by full rrset
@@ -1062,27 +1291,33 @@ class SessionTest(unittest.TestCase):
RRType.NS(),
orig_ns_rrset)
# but the MX should be gone
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
- isc.dns.Name("example.org"),
- RRType.MX())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+ isc.dns.Name("example.org"),
+ RRType.MX())
# Deleting the NS rrset by name and type only, it should also be left
# untouched
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_ns_apex ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.NS(),
- orig_ns_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.NS(),
+ orig_ns_rrset)
+ def DISABLED_test_update_apex_special_case_ns_rrset(self):
# If we delete the NS at the apex specifically, it should still
# keep one record
+ self.__initialize_update_rrsets()
+ # When we are done, we should have a reduced NS rrset
+ short_ns_rrset = create_rrset("example.org", TEST_RRCLASS,
+ RRType.NS(), 3600,
+ [ "ns3.example.org." ])
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_rrset_ns ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.NS(),
- short_ns_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.NS(),
+ short_ns_rrset)
def test_update_delete_normal_rrset_at_apex(self):
'''
@@ -1090,64 +1325,64 @@ class SessionTest(unittest.TestCase):
'''
# MX should simply be deleted
- self.initialize_update_rrsets()
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("example.org"),
- RRType.MX())
+ self.__initialize_update_rrsets()
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("example.org"),
+ RRType.MX())
self.check_full_handle_result(Rcode.NOERROR(),
[ self.rrset_update_del_rrset_mx ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
- isc.dns.Name("example.org"),
- RRType.MX())
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.NXRRSET,
+ isc.dns.Name("example.org"),
+ RRType.MX())
def test_update_cname_special_cases(self):
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
# Sanity check
orig_cname_rrset = create_rrset("cname.example.org", TEST_RRCLASS,
RRType.CNAME(), 3600,
[ "www.example.org." ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
- isc.dns.Name("cname.example.org"),
- RRType.A(),
- orig_cname_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+ isc.dns.Name("cname.example.org"),
+ RRType.A(),
+ orig_cname_rrset)
# If we try to add data where a cname is preset
rrset = create_rrset("cname.example.org", TEST_RRCLASS, RRType.A(),
3600, [ "192.0.2.1" ])
self.check_full_handle_result(Rcode.NOERROR(), [ rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
- isc.dns.Name("cname.example.org"),
- RRType.A(),
- orig_cname_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+ isc.dns.Name("cname.example.org"),
+ RRType.A(),
+ orig_cname_rrset)
# But updating the cname itself should work
new_cname_rrset = create_rrset("cname.example.org", TEST_RRCLASS,
RRType.CNAME(), 3600,
[ "mail.example.org." ])
self.check_full_handle_result(Rcode.NOERROR(), [ new_cname_rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
- isc.dns.Name("cname.example.org"),
- RRType.A(),
- new_cname_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.CNAME,
+ isc.dns.Name("cname.example.org"),
+ RRType.A(),
+ new_cname_rrset)
- self.initialize_update_rrsets()
+ self.__initialize_update_rrsets()
# Likewise, adding a cname where other data is
# present should do nothing either
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- self.orig_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ self.orig_a_rrset)
new_cname_rrset = create_rrset("www.example.org", TEST_RRCLASS,
RRType.CNAME(), 3600,
[ "mail.example.org." ])
self.check_full_handle_result(Rcode.NOERROR(), [ new_cname_rrset ])
- self.check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
- isc.dns.Name("www.example.org"),
- RRType.A(),
- self.orig_a_rrset)
+ self.__check_inzone_data(isc.datasrc.ZoneFinder.SUCCESS,
+ isc.dns.Name("www.example.org"),
+ RRType.A(),
+ self.orig_a_rrset)
def test_update_bad_class(self):
rrset = create_rrset("example.org.", RRClass.CH(), RRType.TXT(), 0,
@@ -1157,9 +1392,63 @@ class SessionTest(unittest.TestCase):
def test_uncaught_exception(self):
def my_exc():
raise Exception("foo")
- self.__session._UpdateSession__update_soa = my_exc
+ self._session._UpdateSession__update_soa = my_exc
self.assertEqual(Rcode.SERVFAIL().to_text(),
- self.__session._UpdateSession__do_update().to_text())
+ self._session._UpdateSession__do_update().to_text())
+
+class SessionACLTest(SessionTestBase):
+ '''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")
diff --git a/src/lib/python/isc/ddns/tests/zone_config_tests.py b/src/lib/python/isc/ddns/tests/zone_config_tests.py
index 7d4bc76..4efd1c1 100644
--- a/src/lib/python/isc/ddns/tests/zone_config_tests.py
+++ b/src/lib/python/isc/ddns/tests/zone_config_tests.py
@@ -14,15 +14,23 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import isc.log
-import unittest
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.
@@ -93,7 +101,7 @@ class ZoneConfigTest(unittest.TestCase):
# 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)))
+ 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),
@@ -102,14 +110,55 @@ class ZoneConfigTest(unittest.TestCase):
(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)))
+ 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)))
+ 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")
diff --git a/src/lib/python/isc/ddns/zone_config.py b/src/lib/python/isc/ddns/zone_config.py
index 5b1af0c..388770c 100644
--- a/src/lib/python/isc/ddns/zone_config.py
+++ b/src/lib/python/isc/ddns/zone_config.py
@@ -13,6 +13,7 @@
# 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
@@ -33,7 +34,7 @@ class ZoneConfig:
until the details are fixed.
'''
- def __init__(self, secondaries, datasrc_class, datasrc_client):
+ def __init__(self, secondaries, datasrc_class, datasrc_client, acl_map={}):
'''Constructor.
Parameters:
@@ -45,6 +46,11 @@ class ZoneConfig:
- 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()
@@ -52,6 +58,8 @@ class ZoneConfig:
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.'''
@@ -62,3 +70,32 @@ class ZoneConfig:
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/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index 170eee6..ec29b7a 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -17,6 +17,7 @@ check-local:
chmod +x $(abs_builddir)/log_console.py
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(abs_srcdir)/check_output.sh $(abs_builddir)/log_console.py $(abs_srcdir)/console.out
if ENABLE_PYTHON_COVERAGE
touch $(abs_top_srcdir)/.coverage
@@ -28,6 +29,7 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done ; \
for pytest in $(PYTESTS_GEN) ; do \
@@ -36,5 +38,6 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/log/python/.libs \
B10_TEST_PLUGIN_DIR=$(abs_top_srcdir)/src/bin/cfgmgr/plugins \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
index 4829edc..3ea96da 100644
--- a/src/lib/python/isc/server_common/tests/Makefile.am
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -20,5 +20,6 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
index 2d3f937..2d6d43d 100644
--- a/src/lib/python/isc/xfrin/diff.py
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -25,6 +25,7 @@ But for now, it lives here.
import isc.dns
import isc.log
+from isc.datasrc import ZoneFinder
from isc.log_messages.libxfrin_messages import *
class NoSuchZone(Exception):
@@ -119,7 +120,7 @@ class Diff:
else:
self.__buffer = []
- def __check_commited(self):
+ def __check_committed(self):
"""
This checks if the diff is already commited or broken. If it is, it
raises ValueError. This check is for methods that need to work only on
@@ -169,7 +170,7 @@ class Diff:
- in single_update_mode if any later rr is of type SOA (both for
addition and deletion)
"""
- self.__check_commited()
+ self.__check_committed()
if rr.get_rdata_count() != 1:
raise ValueError('The rrset must contain exactly 1 Rdata, but ' +
'it holds ' + str(rr.get_rdata_count()))
@@ -298,7 +299,7 @@ class Diff:
else:
raise ValueError('Unknown operation ' + operation)
- self.__check_commited()
+ self.__check_committed()
# First, compact the data
self.compact()
try:
@@ -330,7 +331,7 @@ class Diff:
This might raise isc.datasrc.Error.
"""
- self.__check_commited()
+ self.__check_committed()
# Push the data inside the data source
self.apply()
# Make sure they are visible.
@@ -376,3 +377,33 @@ class Diff:
raise ValueError("Separate buffers requested in single-update mode")
else:
return (self.__deletions, self.__additions)
+
+ def find(self, name, rrtype,
+ options=(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK)):
+ """
+ Calls the find() method in the ZoneFinder associated with this
+ Diff's ZoneUpdater, i.e. the find() on the zone as it was on the
+ moment this Diff object got created.
+ See the ZoneFinder documentation for a full description.
+ Note that the result does not include changes made in this Diff
+ instance so far.
+ Options default to NO_WILDCARD and FIND_GLUE_OK.
+ Raises a ValueError if the Diff has been committed already
+ """
+ self.__check_committed()
+ return self.__updater.find(name, rrtype, options)
+
+ def find_all(self, name,
+ options=(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK)):
+ """
+ Calls the find() method in the ZoneFinder associated with this
+ Diff's ZoneUpdater, i.e. the find_all() on the zone as it was on the
+ moment this Diff object got created.
+ See the ZoneFinder documentation for a full description.
+ Note that the result does not include changes made in this Diff
+ instance so far.
+ Options default to NO_WILDCARD and FIND_GLUE_OK.
+ Raises a ValueError if the Diff has been committed already
+ """
+ self.__check_committed()
+ return self.__updater.find_all(name, options)
diff --git a/src/lib/python/isc/xfrin/tests/Makefile.am b/src/lib/python/isc/xfrin/tests/Makefile.am
index 416d62b..459efc3 100644
--- a/src/lib/python/isc/xfrin/tests/Makefile.am
+++ b/src/lib/python/isc/xfrin/tests/Makefile.am
@@ -20,5 +20,6 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/xfrin/tests/diff_tests.py b/src/lib/python/isc/xfrin/tests/diff_tests.py
index 6c545d2..36339fd 100644
--- a/src/lib/python/isc/xfrin/tests/diff_tests.py
+++ b/src/lib/python/isc/xfrin/tests/diff_tests.py
@@ -15,7 +15,7 @@
import isc.log
import unittest
-import isc.datasrc
+from isc.datasrc import ZoneFinder
from isc.dns import Name, RRset, RRClass, RRType, RRTTL, Rdata
from isc.xfrin.diff import Diff, NoSuchZone
@@ -48,6 +48,13 @@ class DiffTest(unittest.TestCase):
self.__broken_called = False
self.__warn_called = False
self.__should_replace = False
+ self.__find_called = False
+ self.__find_name = None
+ self.__find_type = None
+ self.__find_options = None
+ self.__find_all_called = False
+ self.__find_all_name = None
+ self.__find_all_options = None
# Some common values
self.__rrclass = RRClass.IN()
self.__type = RRType.A()
@@ -156,6 +163,23 @@ class DiffTest(unittest.TestCase):
return self
+ def find(self, name, rrtype, options=None):
+ self.__find_called = True
+ self.__find_name = name
+ self.__find_type = rrtype
+ self.__find_options = options
+ # Doesn't really matter what is returned, as long
+ # as the test can check that it's passed along
+ return "find_return"
+
+ def find_all(self, name, options=None):
+ self.__find_all_called = True
+ self.__find_all_name = name
+ self.__find_all_options = options
+ # Doesn't really matter what is returned, as long
+ # as the test can check that it's passed along
+ return "find_all_return"
+
def test_create(self):
"""
This test the case when the diff is successfuly created. It just
@@ -265,6 +289,9 @@ class DiffTest(unittest.TestCase):
self.assertRaises(ValueError, diff.commit)
self.assertRaises(ValueError, diff.add_data, self.__rrset2)
self.assertRaises(ValueError, diff.delete_data, self.__rrset1)
+ self.assertRaises(ValueError, diff.find, Name('foo.example.org.'),
+ RRType.A())
+ self.assertRaises(ValueError, diff.find_all, Name('foo.example.org.'))
diff.apply = orig_apply
self.assertRaises(ValueError, diff.apply)
# This one does not state it should raise, so check it doesn't
@@ -587,6 +614,66 @@ class DiffTest(unittest.TestCase):
self.assertRaises(ValueError, diff.add_data, a)
self.assertRaises(ValueError, diff.delete_data, a)
+ def test_find(self):
+ diff = Diff(self, Name('example.org.'))
+ name = Name('www.example.org.')
+ rrtype = RRType.A()
+
+ self.assertFalse(self.__find_called)
+ self.assertEqual(None, self.__find_name)
+ self.assertEqual(None, self.__find_type)
+ self.assertEqual(None, self.__find_options)
+
+ self.assertEqual("find_return", diff.find(name, rrtype))
+
+ self.assertTrue(self.__find_called)
+ self.assertEqual(name, self.__find_name)
+ self.assertEqual(rrtype, self.__find_type)
+ self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
+ self.__find_options)
+
+ def test_find_options(self):
+ diff = Diff(self, Name('example.org.'))
+ name = Name('foo.example.org.')
+ rrtype = RRType.TXT()
+ options = ZoneFinder.NO_WILDCARD
+
+ self.assertEqual("find_return", diff.find(name, rrtype, options))
+
+ self.assertTrue(self.__find_called)
+ self.assertEqual(name, self.__find_name)
+ self.assertEqual(rrtype, self.__find_type)
+ self.assertEqual(options, self.__find_options)
+
+ def test_find_all(self):
+ diff = Diff(self, Name('example.org.'))
+ name = Name('www.example.org.')
+
+ self.assertFalse(self.__find_all_called)
+ self.assertEqual(None, self.__find_all_name)
+ self.assertEqual(None, self.__find_all_options)
+
+ self.assertEqual("find_all_return", diff.find_all(name))
+
+ self.assertTrue(self.__find_all_called)
+ self.assertEqual(name, self.__find_all_name)
+ self.assertEqual(ZoneFinder.NO_WILDCARD | ZoneFinder.FIND_GLUE_OK,
+ self.__find_all_options)
+
+ def test_find_all_options(self):
+ diff = Diff(self, Name('example.org.'))
+ name = Name('www.example.org.')
+ options = isc.datasrc.ZoneFinder.NO_WILDCARD
+
+ self.assertFalse(self.__find_all_called)
+ self.assertEqual(None, self.__find_all_name)
+ self.assertEqual(None, self.__find_all_options)
+
+ self.assertEqual("find_all_return", diff.find_all(name, options))
+
+ self.assertTrue(self.__find_all_called)
+ self.assertEqual(name, self.__find_all_name)
+ self.assertEqual(options, self.__find_all_options)
if __name__ == "__main__":
isc.log.init("bind10")
diff --git a/src/lib/testutils/socket_request.h b/src/lib/testutils/socket_request.h
index 5c76d30..0ae15f3 100644
--- a/src/lib/testutils/socket_request.h
+++ b/src/lib/testutils/socket_request.h
@@ -55,7 +55,7 @@ public:
/// \param expect_port The port which is expected to be requested. If
/// the application requests a different port, it is considered
/// a failure.
- /// \param expeted_app The share name for which all the requests should
+ /// \param expected_app The share name for which all the requests should
/// be made. This is not the usual app_name - the requestSocket does
/// not fall back to this value if its share_name is left empty, if
/// you want to check the code relies on the requestor to use the
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index c2b3020..fad2465 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -4,6 +4,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exceptions
AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DLOCKFILE_DIR=\"${localstatedir}/${PACKAGE_NAME}\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
lib_LTLIBRARIES = libutil.la
@@ -12,6 +13,9 @@ libutil_la_SOURCES += locks.h lru_list.h
libutil_la_SOURCES += strutil.h strutil.cc
libutil_la_SOURCES += buffer.h io_utilities.h
libutil_la_SOURCES += time_utilities.h time_utilities.cc
+libutil_la_SOURCES += interprocess_sync.h
+libutil_la_SOURCES += interprocess_sync_file.h interprocess_sync_file.cc
+libutil_la_SOURCES += interprocess_sync_null.h interprocess_sync_null.cc
libutil_la_SOURCES += range_utilities.h
libutil_la_SOURCES += hash/sha1.h hash/sha1.cc
libutil_la_SOURCES += encode/base16_from_binary.h
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
index 1263636..7e88108 100644
--- a/src/lib/util/buffer.h
+++ b/src/lib/util/buffer.h
@@ -206,8 +206,8 @@ public:
/// If specified buffer is too short, it will be expanded
/// using vector::resize() method.
///
- /// @param Reference to a buffer (data will be stored there).
- /// @param Size specified number of bytes to read in a vector.
+ /// @param data Reference to a buffer (data will be stored there).
+ /// @param len Size specified number of bytes to read in a vector.
///
void readVector(std::vector<uint8_t>& data, size_t len) {
if (position_ + len > len_) {
diff --git a/src/lib/util/interprocess_sync.h b/src/lib/util/interprocess_sync.h
new file mode 100644
index 0000000..e4fa7af
--- /dev/null
+++ b/src/lib/util/interprocess_sync.h
@@ -0,0 +1,149 @@
+// 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 __INTERPROCESS_SYNC_H__
+#define __INTERPROCESS_SYNC_H__
+
+#include <string>
+
+namespace isc {
+namespace util {
+
+class InterprocessSyncLocker; // forward declaration
+
+/// \brief Interprocess Sync Class
+///
+/// This class specifies an interface for mutual exclusion among
+/// co-operating processes. This is an abstract class and a real
+/// implementation such as InterprocessSyncFile should be used
+/// in code. Usage is as follows:
+///
+/// 1. Client instantiates a sync object of an implementation (such as
+/// InterprocessSyncFile).
+/// 2. Client then creates an automatic (stack) object of
+/// InterprocessSyncLocker around the sync object. Such an object
+/// destroys itself and releases any acquired lock when it goes out of extent.
+/// 3. Client calls lock() method on the InterprocessSyncLocker.
+/// 4. Client performs task that needs mutual exclusion.
+/// 5. Client frees lock with unlock(), or simply returns from the basic
+/// block which forms the scope for the InterprocessSyncLocker.
+///
+/// NOTE: All implementations of InterprocessSync should keep the
+/// is_locked_ member variable updated whenever their
+/// lock()/tryLock()/unlock() implementations are called.
+class InterprocessSync {
+ // InterprocessSyncLocker is the only code outside this class that
+ // should be allowed to call the lock(), tryLock() and unlock()
+ // methods.
+ friend class InterprocessSyncLocker;
+
+public:
+ /// \brief Constructor
+ ///
+ /// Creates an interprocess synchronization object
+ ///
+ /// \param task_name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSync(const std::string& task_name) :
+ task_name_(task_name), is_locked_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSync() {}
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool lock() = 0;
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool tryLock() = 0;
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ virtual bool unlock() = 0;
+
+ const std::string task_name_; ///< The task name
+ bool is_locked_; ///< Is the lock taken?
+};
+
+/// \brief Interprocess Sync Locker Class
+///
+/// This class is used for making automatic stack objects to manage
+/// locks that are released automatically when the block is exited
+/// (RAII). It is meant to be used along with InterprocessSync objects. See
+/// the description of InterprocessSync.
+class InterprocessSyncLocker {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a lock manager around a interprocess synchronization object
+ ///
+ /// \param sync The sync object which has to be locked/unlocked by
+ /// this locker object.
+ InterprocessSyncLocker(InterprocessSync& sync) :
+ sync_(sync)
+ {}
+
+ /// \brief Destructor
+ ~InterprocessSyncLocker() {
+ if (isLocked())
+ unlock();
+ }
+
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock() {
+ return (sync_.lock());
+ }
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if a new lock could be acquired, false
+ /// otherwise.
+ bool tryLock() {
+ return (sync_.tryLock());
+ }
+
+ /// \brief Check if the lock is taken
+ ///
+ /// \return Returns true if a lock is currently acquired, false
+ /// otherwise.
+ bool isLocked() const {
+ return (sync_.is_locked_);
+ }
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock() {
+ return (sync_.unlock());
+ }
+
+protected:
+ InterprocessSync& sync_; ///< Ref to underlying sync object
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // __INTERPROCESS_SYNC_H__
diff --git a/src/lib/util/interprocess_sync_file.cc b/src/lib/util/interprocess_sync_file.cc
new file mode 100644
index 0000000..d045449
--- /dev/null
+++ b/src/lib/util/interprocess_sync_file.cc
@@ -0,0 +1,130 @@
+// 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.
+
+#include "interprocess_sync_file.h"
+
+#include <string>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace isc {
+namespace util {
+
+InterprocessSyncFile::~InterprocessSyncFile() {
+ if (fd_ != -1) {
+ // This will also release any applied locks.
+ close(fd_);
+ // The lockfile will continue to exist, and we must not delete
+ // it.
+ }
+}
+
+bool
+InterprocessSyncFile::do_lock(int cmd, short l_type) {
+ // Open lock file only when necessary (i.e., here). This is so that
+ // if a default InterprocessSync object is replaced with another
+ // implementation, it doesn't attempt any opens.
+ if (fd_ == -1) {
+ std::string lockfile_path = LOCKFILE_DIR;
+
+ const char* const env = getenv("B10_FROM_BUILD");
+ if (env != NULL) {
+ lockfile_path = env;
+ }
+
+ const char* const env2 = getenv("B10_FROM_BUILD_LOCALSTATEDIR");
+ if (env2 != NULL) {
+ lockfile_path = env2;
+ }
+
+ const char* const env3 = getenv("B10_LOCKFILE_DIR_FROM_BUILD");
+ if (env3 != NULL) {
+ lockfile_path = env3;
+ }
+
+ lockfile_path += "/" + task_name_ + "_lockfile";
+
+ // Open the lockfile in the constructor so it doesn't do the access
+ // checks every time a message is logged.
+ const mode_t mode = umask(0111);
+ fd_ = open(lockfile_path.c_str(), O_CREAT | O_RDWR, 0660);
+ umask(mode);
+
+ if (fd_ == -1) {
+ isc_throw(InterprocessSyncFileError,
+ "Unable to use interprocess sync lockfile: " +
+ lockfile_path);
+ }
+ }
+
+ struct flock lock;
+
+ memset(&lock, 0, sizeof (lock));
+ lock.l_type = l_type;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+
+ return (fcntl(fd_, cmd, &lock) == 0);
+}
+
+bool
+InterprocessSyncFile::lock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::tryLock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLK, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::unlock() {
+ if (!is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_UNLCK)) {
+ is_locked_ = false;
+ return (true);
+ }
+
+ return (false);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/interprocess_sync_file.h b/src/lib/util/interprocess_sync_file.h
new file mode 100644
index 0000000..fd8da1b
--- /dev/null
+++ b/src/lib/util/interprocess_sync_file.h
@@ -0,0 +1,91 @@
+// 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 __INTERPROCESS_SYNC_FILE_H__
+#define __INTERPROCESS_SYNC_FILE_H__
+
+#include <util/interprocess_sync.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+/// \brief InterprocessSyncFileError
+///
+/// Exception that is thrown if it's not possible to open the
+/// lock file.
+class InterprocessSyncFileError : public Exception {
+public:
+ InterprocessSyncFileError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief File-based Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a file-based
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+///
+/// An InterprocessSyncFileError exception may be thrown if there is an
+/// issue opening the lock file.
+///
+/// Lock files are created typically in the local state directory
+/// (var). They are typically named like "<task_name>_lockfile".
+/// This implementation opens lock files lazily (only when
+/// necessary). It also leaves the lock files lying around as multiple
+/// processes may have locks on them.
+class InterprocessSyncFile : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a file-based interprocess synchronization object
+ ///
+ /// \param name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncFile(const std::string& task_name) :
+ InterprocessSync(task_name), fd_(-1)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncFile();
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock();
+
+private:
+ bool do_lock(int cmd, short l_type);
+
+ int fd_; ///< The descriptor for the open file
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // __INTERPROCESS_SYNC_FILE_H__
diff --git a/src/lib/util/interprocess_sync_null.cc b/src/lib/util/interprocess_sync_null.cc
new file mode 100644
index 0000000..5355d57
--- /dev/null
+++ b/src/lib/util/interprocess_sync_null.cc
@@ -0,0 +1,42 @@
+// 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.
+
+#include "interprocess_sync_null.h"
+
+namespace isc {
+namespace util {
+
+InterprocessSyncNull::~InterprocessSyncNull() {
+}
+
+bool
+InterprocessSyncNull::lock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::tryLock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::unlock() {
+ is_locked_ = false;
+ return (true);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/interprocess_sync_null.h b/src/lib/util/interprocess_sync_null.h
new file mode 100644
index 0000000..6ac0322
--- /dev/null
+++ b/src/lib/util/interprocess_sync_null.h
@@ -0,0 +1,64 @@
+// 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 __INTERPROCESS_SYNC_NULL_H__
+#define __INTERPROCESS_SYNC_NULL_H__
+
+#include <util/interprocess_sync.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Null Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a null (no effect)
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+class InterprocessSyncNull : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a null interprocess synchronization object
+ ///
+ /// \param name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncNull(const std::string& task_name) :
+ InterprocessSync(task_name)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncNull();
+
+protected:
+ /// \brief Acquire the lock (never blocks)
+ ///
+ /// \return Always returns true
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Always returns true
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Always returns true
+ bool unlock();
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // __INTERPROCESS_SYNC_NULL_H__
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 37dfc5e..cf1e5a5 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -28,6 +28,8 @@ run_unittests_SOURCES += filename_unittest.cc
run_unittests_SOURCES += hex_unittest.cc
run_unittests_SOURCES += io_utilities_unittest.cc
run_unittests_SOURCES += lru_list_unittest.cc
+run_unittests_SOURCES += interprocess_sync_file_unittest.cc
+run_unittests_SOURCES += interprocess_sync_null_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_SOURCES += random_number_generator_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
diff --git a/src/lib/util/tests/interprocess_sync_file_unittest.cc b/src/lib/util/tests/interprocess_sync_file_unittest.cc
new file mode 100644
index 0000000..9a1b025
--- /dev/null
+++ b/src/lib/util/tests/interprocess_sync_file_unittest.cc
@@ -0,0 +1,174 @@
+// 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.
+
+#include "util/interprocess_sync_file.h"
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+
+namespace {
+unsigned char
+parentReadLockedState (int fd) {
+ unsigned char locked = 0xff;
+
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ // We use select() here to wait for new data on the input end of
+ // the pipe. We wait for 5 seconds (an arbitrary value) for input
+ // data, and continue if no data is available. This is done so
+ // that read() is not blocked due to some issue in the child
+ // process (and the tests continue running).
+
+ struct timeval tv;
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+
+ const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+ EXPECT_EQ(1, nfds);
+
+ if (nfds == 1) {
+ // Read status
+ ssize_t bytes_read = read(fd, &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_read);
+ }
+
+ return (locked);
+}
+
+TEST(InterprocessSyncFileTest, TestLock) {
+ InterprocessSyncFile sync("test");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ int fds[2];
+
+ // Here, we check that a lock has been taken by forking and
+ // checking from the child that a lock exists. This has to be
+ // done from a separate process as we test by trying to lock the
+ // range again on the lock file. The lock attempt would pass if
+ // done from the same process for the granted range. The lock
+ // attempt must fail to pass our check.
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (!locker2.tryLock()) {
+ EXPECT_FALSE(locker2.isLocked());
+ locked = 1;
+ } else {
+ EXPECT_TRUE(locker2.isLocked());
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadLockedState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(1, locked);
+ }
+
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+ EXPECT_TRUE(locker2.lock());
+ EXPECT_TRUE(locker2.unlock());
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ int fds[2];
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0xff;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (locker2.tryLock()) {
+ locked = 0;
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadLockedState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(0, locked);
+ }
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+}
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/tests/interprocess_sync_null_unittest.cc b/src/lib/util/tests/interprocess_sync_null_unittest.cc
new file mode 100644
index 0000000..70e2b07
--- /dev/null
+++ b/src/lib/util/tests/interprocess_sync_null_unittest.cc
@@ -0,0 +1,76 @@
+// 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.
+
+#include "util/interprocess_sync_null.h"
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+
+TEST(InterprocessSyncNullTest, TestNull) {
+ InterprocessSyncNull sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ // Check if the is_locked_ flag is set correctly during lock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // lock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+
+ // Check if the is_locked_ flag is set correctly during unlock().
+ EXPECT_TRUE(locker.isLocked());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ // unlock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+
+ // Check if the is_locked_ flag is set correctly during tryLock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // tryLock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+
+ // Random order (should all return true)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/tests/run_unittests.cc b/src/lib/util/tests/run_unittests.cc
index a2181cf..8789a9c 100644
--- a/src/lib/util/tests/run_unittests.cc
+++ b/src/lib/util/tests/run_unittests.cc
@@ -14,10 +14,12 @@
#include <gtest/gtest.h>
#include <util/unittests/run_all.h>
+#include <stdlib.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
+ setenv("B10_LOCKFILE_DIR_FROM_BUILD", TEST_DATA_TOPBUILDDIR, 1);
return (isc::util::unittests::run_all());
}
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 685ff1b..12383ac 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -12,19 +12,12 @@ if USE_STATIC_LINK
AM_LDFLAGS += -static
endif
-# We have to suppress warnings because we are compiling C code with CXX
-# We have to do this to link with new C++ pieces of code
-perfdhcp_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_CLANGPP
-perfdhcp_CXXFLAGS += -Wno-error
-else
-if USE_GXX
-perfdhcp_CXXFLAGS += -Wno-write-strings
-endif
-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.cc
-perfdhcp_SOURCES += command_options.cc command_options.h
+perfdhcp_SOURCES = perfdhcp.c
+
-perfdhcp_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/tests/tools/perfdhcp/perfdhcp.cc b/tests/tools/perfdhcp/perfdhcp.cc
deleted file mode 100644
index 9358a35..0000000
--- a/tests/tools/perfdhcp/perfdhcp.cc
+++ /dev/null
@@ -1,3565 +0,0 @@
-/*
- * 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 <config.h>
-
-#ifndef HAVE_GETIFADDRS
-
-/*
- * Solaris 10 does not have the getifaddrs() function available (although it
- * is present on Solaris 11 and later). For the present we will not implement
- * a replacement (as we do with clock_gettime) as the implementation is
- * relatively complex. Just output a message saying that the utility is not
- * supported on this operating system.
- */
-
-#include <stdio.h>
-
-int
-main(const int, char* const*)
-{
- fprintf(stderr, "perfdhcp is not supported on this version of the operating system\n");
- return (1);
-}
-
-#else
-
-/* getifaddrs() present, so the code should compile */
-
-#define __STDC_LIMIT_MACROS
-
-#ifdef __linux__
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#endif
-
-#include <sys/types.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
-
-#include <net/if.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <ifaddrs.h>
-#include <math.h>
-#include <netdb.h>
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-
-#ifndef HAVE_PSELECT
-
-#include <assert.h>
-
-/* Platforms such as OpenBSD don't provide a pselect(), so we use our
- own implementation for this testcase, which wraps around select() and
- hence doesn't implement the high precision timer. This implementation
- is fine for our purpose. */
-
-static int
-pselect (int nfds, fd_set *readfds, fd_set *writefds,
- fd_set *exceptfds, const struct timespec *timeout,
- const sigset_t *sigmask)
-{
- struct timeval my_timeout;
-
- /* Our particular usage of pselect() doesn't use these fields. */
- assert(writefds == NULL);
- assert(exceptfds == NULL);
- assert(sigmask == NULL);
-
- my_timeout.tv_sec = timeout->tv_sec;
- my_timeout.tv_usec = timeout->tv_nsec / 1000;
-
- return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
-}
-
-#endif /* !HAVE_PSELECT */
-
-/* DHCPv4 defines (to be moved/shared) */
-
-#define DHCP_OFF_OPCODE 0
-#define DHCP_OFF_HTYPE 1
-#define DHCP_OFF_HLEN 2
-#define DHCP_OFF_HOPS 3
-#define DHCP_OFF_XID 4
-#define DHCP_OFF_SECS 8
-#define DHCP_OFF_FLAGS 10
-#define DHCP_OFF_CIADDR 12
-#define DHCP_OFF_YIADDR 16
-#define DHCP_OFF_SADDR 20
-#define DHCP_OFF_GIADDR 24
-#define DHCP_OFF_CHADDR 28
-#define DHCP_OFF_SNAME 44
-#define DHCP_OFF_FILE 108
-#define DHCP_OFF_COOKIE 236
-#define DHCP_OFF_OPTIONS 240
-
-#define BOOTP_OP_REQUEST 1
-#define BOOTP_OP_REPLY 2
-#define BOOTP_MIN_LEN 300
-
-#define DHCP_OP_DISCOVER 1
-#define DHCP_OP_OFFER 2
-#define DHCP_OP_REQUEST 3
-#define DHCP_OP_DECLINE 4
-#define DHCP_OP_ACK 5
-#define DHCP_OP_NAK 6
-#define DHCP_OP_RELEASE 7
-#define DHCP_OP_INFORM 8
-
-#define DHCP_HTYPE_ETHER 1
-
-#define DHCP_OPT_PAD 0
-#define DHCP_OPT_SUBNET_MASK 1
-#define DHCP_OPT_TIME_OFFSET 2
-#define DHCP_OPT_ROUTERS 3
-#define DHCP_OPT_DNS_SERVERS 6
-#define DHCP_OPT_HOST_NAME 12
-#define DHCP_OPT_DOMAIN_NAME 15
-#define DHCP_OPT_BROADCAST 28
-#define DHCP_OPT_DHCP_ADDRESS 50
-#define DHCP_OPT_DHCP_LEASE 51
-#define DHCP_OPT_DHCP_MSGTYPE 53
-#define DHCP_OPT_DHCP_SRVID 54
-#define DHCP_OPT_DHCP_PRL 55
-#define DHCP_OPT_END 255
-
-#define DHCP_OPTLEN_SRVID 6
-
-/* DHCPv6 defines (to be moved/shared) */
-
-#define DHCP6_OFF_MSGTYP 0
-#define DHCP6_OFF_XID 1
-#define DHCP6_OFF_OPTIONS 4
-
-#define DHCP6_OP_SOLICIT 1
-#define DHCP6_OP_ADVERTISE 2
-#define DHCP6_OP_REQUEST 3
-#define DHCP6_OP_REPLY 7
-
-#define DHCP6_OPT_CLIENTID 1
-#define DHCP6_OPT_SERVERID 2
-#define DHCP6_OPT_IA_NA 3
-#define DHCP6_OPT_ORO 6
-#define DHCP6_OPT_ELAPSED_TIME 8
-#define DHCP6_OPT_STATUS_CODE 13
-#define DHCP6_OPT_RAPID_COMMIT 14
-#define DHCP6_OPT_NAME_SERVERS 23
-#define DHCP6_OPT_DOMAIN_SEARCH 24
-
-#define DHCP6_ST_SUCCESS 0
-#define DHCP6_ST_NOADDRSAVAIL 2
-
-#define DHCP6_DUID_LLT 1
-#define DHCP6_DUID_EPOCH 946684800
-
-/* tail queue macros (from FreeBSD 8.2 /sys/sys/queue.h, to be moved/shared) */
-
-#define ISC_TAILQ_HEAD(name, type) \
-struct name { \
- struct type *tqh_first; \
- struct type **tqh_last; \
-}
-
-#define ISC_TAILQ_ENTRY(type) \
-struct { \
- struct type *tqe_next; \
- struct type **tqe_prev; \
-}
-
-#define ISC_TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
-
-#define ISC_TAILQ_FIRST(head) ((head)->tqh_first)
-
-#define ISC_TAILQ_LAST(head, headname) \
- (*(((struct headname *)((head)->tqh_last))->tqh_last))
-
-#define ISC_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
-
-#define ISC_TAILQ_PREV(elm, headname, field) \
- (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
-
-#define ISC_TAILQ_INIT(head) do { \
- ISC_TAILQ_FIRST((head)) = NULL; \
- (head)->tqh_last = &ISC_TAILQ_FIRST((head)); \
-} while (0)
-
-#define ISC_TAILQ_INSERT_HEAD(head, elm, field) do { \
- ISC_TAILQ_NEXT((elm), field) = ISC_TAILQ_FIRST((head)); \
- if (!ISC_TAILQ_EMPTY((head))) \
- ISC_TAILQ_FIRST((head))->field.tqe_prev = \
- &ISC_TAILQ_NEXT((elm), field); \
- else \
- (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
- ISC_TAILQ_FIRST((head)) = (elm); \
- (elm)->field.tqe_prev = &ISC_TAILQ_FIRST((head)); \
-} while (0)
-
-#define ISC_TAILQ_INSERT_TAIL(head, elm, field) do { \
- ISC_TAILQ_NEXT((elm), field) = NULL; \
- (elm)->field.tqe_prev = (head)->tqh_last; \
- *(head)->tqh_last = (elm); \
- (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
-} while (0)
-
-#define ISC_TAILQ_REMOVE(head, elm, field) do { \
- if ((ISC_TAILQ_NEXT((elm), field)) != NULL) \
- ISC_TAILQ_NEXT((elm), field)->field.tqe_prev = \
- (elm)->field.tqe_prev; \
- else \
- (head)->tqh_last = (elm)->field.tqe_prev; \
- *(elm)->field.tqe_prev = ISC_TAILQ_NEXT((elm), field); \
-} while (0)
-
-#define ISC_TAILQ_FOREACH(var, head, field) \
- for ((var) = ISC_TAILQ_FIRST((head)); \
- (var); \
- (var) = ISC_TAILQ_NEXT((var), field))
-
-#define ISC_TAILQ_FOREACH_SAFE(var, head, field, tvar) \
- for ((var) = ISC_TAILQ_FIRST((head)); \
- (var) && ((tvar) = ISC_TAILQ_NEXT((var), field), 1); \
- (var) = (tvar))
-
-/*
- * Data structures
- */
-
-/*
- * exchange:
- * - per exchange values:
- * * order (for debugging)
- * * xid (even/odd for 4 packet exchanges)
- * * random (for debugging)
- * * time-stamps
- * * server ID (for 3rd packet)
- * * IA_NA (for IPv6 3rd packet)
- *
- * sent/rcvd global chains, "next to be received" on entry cache,
- * and hash table for xid -> data structure fast matching
- * (using the assumption collisions are unlikely, cf birthday problem)
- */
-
-struct exchange { /* per exchange structure */
- ISC_TAILQ_ENTRY(exchange) gchain; /* global chaining */
- ISC_TAILQ_ENTRY(exchange) hchain; /* hash table chaining */
- uint64_t order0, order2; /* number of this exchange */
- uint32_t xid; /* transaction ID */
- uint32_t rnd; /* random part */
- struct timespec ts0, ts1, ts2, ts3; /* timespecs */
- uint8_t *sid; /* server ID */
- size_t sidlen; /* server ID length */
- uint8_t *iana; /* (IPv6) IA_NA */
- size_t ianalen; /* (IPv6) IA_NA length */
-};
-struct exchange *xnext0, *xnext2; /* next to be received */
-ISC_TAILQ_HEAD(xlist, exchange); /* exchange list */
-struct xlist xsent0, xsent2, xrcvd0, xrcvd2; /* sent and received lists */
-uint64_t xscount0, xscount2; /* sent counters */
-uint64_t xrcount0, xrcount2; /* received counters */
-caddr_t exchanges0, exchanges2; /* hash tables */
-uint32_t hashsize0, hashsize2; /* hash table sizes */
-
-/*
- * statictics counters and accumulators
- */
-
-uint64_t tooshort, orphans, locallimit; /* error counters */
-uint64_t latesent, compsend, latercvd; /* rate stats */
-uint64_t multrcvd, shortwait, collected[2]; /* rate stats (cont) */
-double dmin0 = 999999999., dmin2 = 999999999.; /* minimum delays */
-double dmax0 = 0., dmax2 = 0.; /* maximum delays */
-double dsum0 = 0., dsum2 = 0.; /* delay sums */
-double dsumsq0 = 0., dsumsq2 = 0.; /* square delay sums */
-
-/*
- * command line parameters
- */
-
-int ipversion = 0; /* IP version */
-int simple; /* DO/SA in place of DORR/SARR */
-int rate; /* rate in exchange per second */
-int report; /* delay between two reports */
-uint32_t range; /* randomization range */
-uint32_t maxrandom; /* maximum random value */
-int basecnt; /* base count */
-char *base[4]; /* bases */
-int gotnumreq; /* numreq[0] was set */
-int numreq[2]; /* number of exchange */
-int period; /* test period */
-int gotlosttime; /* losttime[0] was set */
-double losttime[2] = {1., 1.}; /* time after a request is lost */
-int gotmaxdrop; /* max{p}drop[0] was set */
-int maxdrop[2]; /* maximum number of lost requests */
-double maxpdrop[2] = { 0., 0.}; /* maximum percentage */
-char *localname; /* local address or interface */
-int isinterface; /* interface vs local address */
-int preload; /* preload exchanges */
-int aggressivity = 1; /* back to back exchanges */
-int localport; /* local port number (host endian) */
-int seeded; /* is a seed provided */
-unsigned int seed; /* randomization seed */
-int isbroadcast; /* use broadcast */
-int rapidcommit; /* add rapid commit option */
-int usefirst; /* where to take the server-ID */
-char *templatefile[2]; /* template file name */
-int xidoffset[2] = {-1, -1}; /* template offsets (xid)*/
-int rndoffset[2] = {-1, -1}; /* template offsets (random) */
-int elpoffset = -1; /* template offset (elapsed time) */
-int sidoffset = -1; /* template offset (server ID) */
-int ripoffset = -1; /* template offset (requested IP) */
-char *diags; /* diagnostic selectors */
-char *wrapped; /* wrapped command */
-char *servername; /* server */
-
-/*
- * global variables
- */
-
-struct sockaddr_storage localaddr; /* local socket address */
-struct sockaddr_storage serveraddr; /* server socket address */
-
-int sock; /* socket descriptor */
-int interrupted, fatal; /* to finish flags */
-
-uint8_t obuf[4096], ibuf[4096]; /* I/O buffers */
-char tbuf[8200]; /* template buffer */
-
-struct timespec boot; /* the date of boot */
-struct timespec last; /* the date of last send */
-struct timespec due; /* the date of next send */
-struct timespec dreport; /* the date of next reporting */
-struct timespec finished; /* the date of finish */
-
-uint8_t *gsrvid; /* global server id */
-size_t gsrvidlen; /* and its length */
-uint8_t gsrvidbuf[64]; /* and its storage */
-
-/* MAC address */
-uint8_t mac_prefix[6] = { 0x00, 0x0c, 0x01, 0x02, 0x03, 0x04 };
-
-/* DUID prefix */
-uint8_t *duid_prefix;
-int duid_length;
-
-/* magic cookie for BOOTP/DHCPv4 */
-uint8_t dhcp_cookie[4] = { 0x63, 0x82, 0x53, 0x63 };
-
-/*
- * templates
- *
- * note: the only hard point is what are the offsets:
- * - xid_discover4 and xid_request4: first of the 4 octet long
- * transaction ID (default DHCP_OFF_XID = 4)
- * - random_discover4 and random_request4: last of the 6 octet long
- * MAC address (default DHCP_OFF_CHADDR + 6 = 28 + 6)
- * - elapsed_request4: first of the 2 octet long secs field
- * (default DHCP_OFF_SECS = 8, 0 means disabled)
- * - serverid_request4: first of the 6 octet long server ID option
- * (no default, required)
- * - reqaddr_request4: first of the 4 octet long requested IP address
- * option content (i.e., the address itself, btw OFFER yiaddr)
- * (no default, required)
- * - xid_solicit6 and xid_request6: first of the 3 octet long
- * transaction ID (default DHCP6_OFF_XID = 1)
- * - random_solicit6 and random_request6: last of the DUID in the
- * client ID option (no default, required when rate is set)
- * - elapsed_request6: first of the 2 octet long content of
- * the option elapsed time option (no default, 0 means disabled)
- * - serverid_request6: position where the variable length server ID
- * option is inserted (no default, required, set to length means append)
- * - reqaddr_request6: position where of the variable length requested
- * IP address option is inserted (no default, required, set to
- * length means append)
- */
-
-size_t length_discover4;
-uint8_t template_discover4[4096];
-size_t xid_discover4;
-size_t random_discover4;
-size_t length_request4;
-uint8_t template_request4[4096];
-size_t xid_request4;
-size_t elapsed_request4;
-size_t random_request4;
-size_t serverid_request4;
-size_t reqaddr_request4;
-size_t length_solicit6;
-uint8_t template_solicit6[4096];
-size_t xid_solicit6;
-size_t random_solicit6;
-size_t length_request6;
-uint8_t template_request6[4096];
-size_t xid_request6;
-size_t elapsed_request6;
-size_t random_request6;
-size_t serverid_request6;
-size_t reqaddr_request6;
-
-
-// use definition of CLOCK_REALTIME (or lack of thereof) as an indicator
-// if the code is being compiled or Linux (or somewhere else)
-// Perhaps this should be based on OS_LINUX define?
-
-#if !defined (CLOCK_REALTIME)
-#define CLOCK_REALTIME 0
-
-/// @brief clock_gettime implementation for non-Linux systems
-///
-/// This implementation lacks nanosecond resolution. It is intended
-/// to be used on non-Linux systems that does not provide clock_gettime
-/// implementation.
-///
-/// @param clockid ignored (kept for Linux prototype compatibility)
-/// @param tp timespec structure
-///
-/// @return always zero (kept for compatibility reasons)
-int clock_gettime(int, struct timespec *tp) {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- tp->tv_sec = tv.tv_sec;
- tp->tv_nsec = tv.tv_usec*1000;
-
- return (0);
-}
-
-#endif
-
-/*
- * initialize data structures handling exchanges
- */
-
-void
-inits(void)
-{
- struct xlist *bucket;
- caddr_t p;
- size_t len, i;
-
- ISC_TAILQ_INIT(&xsent0);
- ISC_TAILQ_INIT(&xsent2);
- ISC_TAILQ_INIT(&xrcvd0);
- ISC_TAILQ_INIT(&xrcvd2);
-
- /// compute hashsizes
- hashsize0 = 1024;
- len = sizeof(*bucket) * hashsize0;
- exchanges0 = (caddr_t)malloc(len);
- if (exchanges0 == NULL) {
- perror("malloc(exchanges0)");
- exit(1);
- }
- for (i = 0, p = exchanges0; i < hashsize0; i++, p += sizeof(*bucket)) {
- bucket = (struct xlist *) p;
- ISC_TAILQ_INIT(bucket);
- }
- if (simple != 0)
- return;
- hashsize2 = 1024;
- len = sizeof(*bucket) * hashsize2;
- exchanges2 = (caddr_t)malloc(len);
- if (exchanges2 == NULL) {
- perror("malloc(exchanges2)");
- exit(1);
- }
- for (i = 0, p = exchanges2; i < hashsize2; i++, p += sizeof(*bucket)) {
- bucket = (struct xlist *) p;
- ISC_TAILQ_INIT(bucket);
- }
-}
-
-/*
- * randomize the value of the given field:
- * - offset of the field
- * - random seed (used as it when suitable)
- * - returns the random value which was used
- */
-
-uint32_t
-randomize(size_t offset, uint32_t r)
-{
- uint32_t v;
-
- if (range == 0)
- return 0;
- if (range == UINT32_MAX)
- return r;
- if (maxrandom != 0)
- while (r >= maxrandom)
- r = (uint32_t) random();
- r %= range + 1;
- v = r;
- v += obuf[offset];
- obuf[offset] = v;
- if (v < 256)
- return r;
- v >>= 8;
- v += obuf[offset - 1];
- obuf[offset - 1] = v;
- if (v < 256)
- return r;
- v >>= 8;
- v += obuf[offset - 2];
- obuf[offset - 2] = v;
- if (v < 256)
- return r;
- v >>= 8;
- v += obuf[offset - 3];
- obuf[offset - 3] = v;
- return r;
-}
-
-/*
- * receive a reply (4th packet), shared between IPv4 and IPv6:
- * - transaction ID xid
- * - receiving time-stamp now
- * called from receive[46]() when the xid is odd
- */
-
-void
-receive_reply(uint32_t xid, struct timespec *now)
-{
- struct exchange *x, *t;
- struct xlist *bucket;
- uint32_t hash;
- int checklost;
- double delta;
-
- /* bucket is needed even when the next cache matches */
- hash = (xid >> 1) & (hashsize2 - 1);
- bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
- /* try the 'next to be received' cache */
- if ((xnext2 != NULL) && (xnext2->xid == xid)) {
- x = xnext2;
- goto found;
- }
- /* usually the lost probability is low for request/reply */
- checklost = 1;
- /* look for the exchange */
- ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
- double waited;
-
- if (x->xid == xid)
- goto found;
- if (checklost <= 0)
- continue;
- checklost = 0;
- /* check for a timed-out exchange */
- waited = now->tv_sec - x->ts2.tv_sec;
- waited += (now->tv_nsec - x->ts2.tv_nsec) / 1e9;
- if (waited < losttime[1])
- continue;
- /* garbage collect timed-out exchange */
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent2, x, gchain);
- free(x);
- collected[1] += 1;
- }
- /* no match? very late or not for us */
- orphans++;
- return;
-
- /* got it: update stats and move to the received queue */
- found:
- xrcount2++;
- x->ts3 = *now;
- delta = x->ts3.tv_sec - x->ts2.tv_sec;
- delta += (x->ts3.tv_nsec - x->ts2.tv_nsec) / 1e9;
- if (delta < dmin2)
- dmin2 = delta;
- if (delta > dmax2)
- dmax2 = delta;
- dsum2 += delta;
- dsumsq2 += delta * delta;
- xnext2 = ISC_TAILQ_NEXT(x, gchain);
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent2, x, gchain);
- ISC_TAILQ_INSERT_TAIL(&xrcvd2, x, gchain);
-}
-
-/*
- * get the DHCPv4 socket descriptor
- * (the only complexity is broadcast enabling: there is no easy way to
- * recognize broadcast addresses, so the command line -B flag)
- */
-
-void
-getsock4(void)
-{
- int ret;
-
- /* update local port */
- if (localport != 0) {
- uint16_t lp = htons((uint16_t) localport);
-
- ((struct sockaddr_in *) &localaddr)->sin_port = lp;
- }
- sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (sock < 0) {
- perror("socket");
- exit(1);
- }
- ret = bind(sock,
- (struct sockaddr *) &localaddr,
- sizeof(struct sockaddr_in));
- if (ret < 0) {
- perror("bind");
- exit(1);
- }
- /* enable broadcast if needed or required */
- if (isbroadcast != 0) {
- int on = 1;
-
- ret = setsockopt(sock,
- SOL_SOCKET, SO_BROADCAST,
- &on, sizeof(on));
- if (ret < 0) {
- perror("setsockopt(SO_BROADCAST)");
- exit(1);
- }
- }
-}
-
-/*
- * build a DHCPv4 DISCOVER from a relay template
- * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
- * (assume the link is Ethernet)
- */
-
-void
-build_template_discover4(void)
-{
- uint8_t *p = template_discover4;
-
- length_discover4 = BOOTP_MIN_LEN;
- xid_discover4 = DHCP_OFF_XID;
- random_discover4 = DHCP_OFF_CHADDR + 6;
- /* opcode */
- p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
- /* hardware address type */
- p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
- /* hardware address length */
- p[DHCP_OFF_HLEN] = 6;
- /* hops */
- p[DHCP_OFF_HOPS] = 1;
- /* gateway address */
- memcpy(p + DHCP_OFF_GIADDR,
- &((struct sockaddr_in *) &localaddr)->sin_addr,
- 4);
- /* hardware address */
- memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
- /* cookie */
- memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
- /* options */
- p += DHCP_OFF_OPTIONS;
- /* inline DHCP message type */
- *p++ = DHCP_OPT_DHCP_MSGTYPE;
- *p++ = 1;
- *p++ = DHCP_OP_DISCOVER;
- /* inline DHCP parameter request list (default) */
- *p++ = DHCP_OPT_DHCP_PRL;
- *p++ = 7;
- *p++ = DHCP_OPT_SUBNET_MASK;
- *p++ = DHCP_OPT_BROADCAST;
- *p++ = DHCP_OPT_TIME_OFFSET;
- *p++ = DHCP_OPT_ROUTERS;
- *p++ = DHCP_OPT_DOMAIN_NAME;
- *p++ = DHCP_OPT_DNS_SERVERS;
- *p++ = DHCP_OPT_HOST_NAME;
- /* end */
- *p = DHCP_OPT_END;
-}
-
-/*
- * get a DHCPv4 client/relay first packet (usually a DISCOVER) template
- * from the file given in the command line (-T<template-file>)
- * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
- */
-
-void
-get_template_discover4(void)
-{
- uint8_t *p = template_discover4;
- int fd, cc, i, j;
-
- fd = open(templatefile[0], O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "open(%s): %s\n",
- templatefile[0], strerror(errno));
- exit(2);
- }
- cc = read(fd, tbuf, sizeof(tbuf));
- (void) close(fd);
- if (cc < 0) {
- fprintf(stderr, "read(%s): %s\n",
- templatefile[0], strerror(errno));
- exit(1);
- }
- if (cc < 100) {
- fprintf(stderr, "file '%s' too short\n", templatefile[0]);
- exit(2);
- }
- if (cc > 8193) {
- fprintf(stderr,"file '%s' too large\n", templatefile[0]);
- exit(2);
- }
- j = 0;
- for (i = 0; i < cc; i++) {
- if (isspace((int) tbuf[i]))
- continue;
- if (!isxdigit((int) tbuf[i])) {
- fprintf(stderr,
- "illegal char[%d]='%c' in file '%s'\n",
- i, (int) tbuf[i], templatefile[0]);
- exit(2);
- }
- tbuf[j] = tbuf[i];
- j++;
- }
- cc = j;
- if ((cc & 1) != 0) {
- fprintf(stderr,
- "odd number of hexadecimal digits in file '%s'\n",
- templatefile[0]);
- exit(2);
- }
- length_discover4 = cc >> 1;
- for (i = 0; i < cc; i += 2)
- (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
- if (xidoffset[0] >= 0)
- xid_discover4 = (size_t) xidoffset[0];
- else
- xid_discover4 = DHCP_OFF_XID;
- if (xid_discover4 + 4 > length_discover4) {
- fprintf(stderr,
- "xid (at %zu) outside the template (length %zu)?\n",
- xid_discover4, length_discover4);
- exit(2);
- }
- if (rndoffset[0] >= 0)
- random_discover4 = (size_t) rndoffset[0];
- else
- random_discover4 = DHCP_OFF_CHADDR + 6;
- if (random_discover4 > length_discover4) {
- fprintf(stderr,
- "random (at %zu) outside the template (length %zu)?\n",
- random_discover4, length_discover4);
- exit(2);
- }
-}
-
-/*
- * build a DHCPv4 REQUEST from a relay template
- * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
- * (assume the link is Ethernet)
- */
-
-void
-build_template_request4(void)
-{
- uint8_t *p = template_request4;
-
- length_request4 = BOOTP_MIN_LEN;
- xid_request4 = DHCP_OFF_XID;
- elapsed_request4 = DHCP_OFF_SECS;
- random_request4 = DHCP_OFF_CHADDR + 6;
- /* opcode */
- p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
- /* hardware address type */
- p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
- /* hardware address length */
- p[DHCP_OFF_HLEN] = 6;
- /* hops */
- p[DHCP_OFF_HOPS] = 1;
- /* gateway address */
- memcpy(p + DHCP_OFF_GIADDR,
- &((struct sockaddr_in *) &localaddr)->sin_addr,
- 4);
- /* hardware address */
- memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
- /* cookie */
- memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
- /* options */
- p += DHCP_OFF_OPTIONS;
- /* inline DHCP message type */
- *p++ = DHCP_OPT_DHCP_MSGTYPE;
- *p++ = 1;
- *p++ = DHCP_OP_REQUEST;
- /* place for DHCP server id (option) */
- serverid_request4 = p - template_request4;
- p += DHCP_OPTLEN_SRVID;
- /* place for DHCP requested IP address (address) */
- *p++ = DHCP_OPT_DHCP_ADDRESS;
- *p++ = 4;
- reqaddr_request4 = p - template_request4;
- p += 4;
- /* inline DHCP parameter request list (default) */
- *p++ = DHCP_OPT_DHCP_PRL;
- *p++ = 7;
- *p++ = DHCP_OPT_SUBNET_MASK;
- *p++ = DHCP_OPT_BROADCAST;
- *p++ = DHCP_OPT_TIME_OFFSET;
- *p++ = DHCP_OPT_ROUTERS;
- *p++ = DHCP_OPT_DOMAIN_NAME;
- *p++ = DHCP_OPT_DNS_SERVERS;
- *p++ = DHCP_OPT_HOST_NAME;
- /* end */
- *p = DHCP_OPT_END;
-}
-
-/*
- * get a DHCPv4 client/relay third packet (usually a REQUEST) template
- * from the file given in the command line (-T<template-file>)
- * and offsets (-X,-O,-E,-S,-I).
- */
-
-void
-get_template_request4(void)
-{
- uint8_t *p = template_request4;
- int fd, cc, i, j;
-
- fd = open(templatefile[1], O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "open(%s): %s\n",
- templatefile[1], strerror(errno));
- exit(2);
- }
- cc = read(fd, tbuf, sizeof(tbuf));
- (void) close(fd);
- if (cc < 0) {
- fprintf(stderr, "read(%s): %s\n",
- templatefile[1], strerror(errno));
- exit(1);
- }
- if (cc < 100) {
- fprintf(stderr, "file '%s' too short\n", templatefile[1]);
- exit(2);
- }
- if (cc > 8193) {
- fprintf(stderr,"file '%s' too large\n", templatefile[1]);
- exit(2);
- }
- j = 0;
- for (i = 0; i < cc; i++) {
- if (isspace((int) tbuf[i]))
- continue;
- if (!isxdigit((int) tbuf[i])) {
- fprintf(stderr,
- "illegal char[%d]='%c' in file '%s'\n",
- i, (int) tbuf[i], templatefile[1]);
- exit(2);
- }
- tbuf[j] = tbuf[i];
- j++;
- }
- cc = j;
- if ((cc & 1) != 0) {
- fprintf(stderr,
- "odd number of hexadecimal digits in file '%s'\n",
- templatefile[1]);
- exit(2);
- }
- length_request4 = cc >> 1;
- for (i = 0; i < cc; i += 2)
- (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
- if (xidoffset[1] >= 0)
- xid_request4 = (size_t) xidoffset[1];
- else
- xid_request4 = DHCP_OFF_XID;
- if (xid_request4 + 4 > length_request4) {
- fprintf(stderr,
- "xid (at %zu) outside the template (length %zu)?\n",
- xid_request4, length_request4);
- exit(2);
- }
- if (rndoffset[1] >= 0)
- random_request4 = (size_t) rndoffset[1];
- else
- random_request4 = DHCP_OFF_CHADDR + 6;
- if (random_request4 > length_request4) {
- fprintf(stderr,
- "random (at %zu) outside the template (length %zu)?\n",
- random_request4, length_request4);
- exit(2);
- }
- if (elpoffset >= 0)
- elapsed_request4 = (size_t) elpoffset;
- else
- elapsed_request4 = DHCP_OFF_SECS;
- if (elapsed_request4 + 2 > length_request4) {
- fprintf(stderr,
- "secs (at %zu) outside the template (length %zu)?\n",
- elapsed_request4, length_request4);
- exit(2);
- }
- serverid_request4 = (size_t) sidoffset;
- if (serverid_request4 + 6 > length_request4) {
- fprintf(stderr,
- "server-id option (at %zu) outside the template "
- "(length %zu)?\n",
- serverid_request4, length_request4);
- exit(2);
- }
- reqaddr_request4 = (size_t) ripoffset;
- if (reqaddr_request4 + 4 > length_request4) {
- fprintf(stderr,
- "requested-ip-address option (at %zu) outside "
- "the template (length %zu)?\n",
- reqaddr_request4, length_request4);
- exit(2);
- }
-}
-
-/*
- * send the DHCPv4 REQUEST third packet
- * (the transaction ID is odd)
- * (TODO: check for errors in the OFFER)
- */
-
-void
-send_request4(struct exchange *x0)
-{
- struct exchange *x;
- struct xlist *bucket;
- uint32_t hash;
- ssize_t ret;
-
- x = (struct exchange *) malloc(sizeof(*x));
- if (x == NULL) {
- locallimit++;
- perror("send2");
- return;
- }
-
- memcpy(x, x0, sizeof(*x));
- x->order2 = xscount2++;
- x->xid |= 1;
- hash = x->xid >> 1;
-
- ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
- hash &= hashsize2 - 1;
- bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
- ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
-
- memcpy(obuf, template_request4, length_request4);
- /* xid */
- memcpy(obuf + xid_request4, &x->xid, 4);
- /* random */
- randomize(random_request4, x->rnd);
- /* secs */
- if (elapsed_request4 > 0) {
- int secs;
-
- secs = x->ts1.tv_sec - x->ts0.tv_sec;
- if (x->ts1.tv_nsec < x->ts0.tv_nsec)
- secs += 1;
- if (secs > 0) {
- obuf[elapsed_request4] = secs >> 8;
- obuf[elapsed_request4 + 1] = secs & 0xff;
- }
- }
- /* server ID */
- memcpy(obuf + serverid_request4, x->sid, x->sidlen);
- /* requested IP address */
- memcpy(obuf + reqaddr_request4, ibuf + DHCP_OFF_YIADDR, 4);
-
- /* timestamp */
- ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
- if (ret < 0) {
- perror("clock_gettime(send2)");
- fatal = 1;
- return;
- }
- ret = sendto(sock, obuf, length_request4, 0,
- (struct sockaddr *) &serveraddr,
- sizeof(struct sockaddr_in));
- if (ret >= 0)
- return;
- if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
- (errno == ENOBUFS) || (errno == ENOMEM))
- locallimit++;
- perror("send2");
-}
-
-/*
- * send the DHCPv4 DISCOVER first packet
- * (for 4-exchange, the transaction ID xid is even)
- */
-
-int
-send4(void)
-{
- struct exchange *x;
- struct xlist *bucket;
- uint32_t hash;
- ssize_t ret;
-
- x = (struct exchange *) malloc(sizeof(*x));
- if (x == NULL)
- return -ENOMEM;
-
- memset(x, 0, sizeof(*x));
- x->order0 = xscount0++;
- hash = x->rnd = (uint32_t) random();
- if (simple == 0)
- x->xid = hash << 1;
- else
- x->xid = hash;
-
- ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
- hash &= hashsize0 - 1;
- bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
- ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
-
- memcpy(obuf, template_discover4, length_discover4);
- /* xid */
- memcpy(obuf + xid_discover4, &x->xid, 4);
- /* random */
- x->rnd = randomize(random_discover4, x->rnd);
- /* timestamp */
- ret = clock_gettime(CLOCK_REALTIME, &last);
- if (ret < 0) {
- perror("clock_gettime(send)");
- fatal = 1;
- return -errno;
- }
- x->ts0 = last;
- errno = 0;
- ret = sendto(sock, obuf, length_discover4, 0,
- (struct sockaddr *) &serveraddr,
- sizeof(struct sockaddr_in));
- if (ret == (ssize_t) length_discover4)
- return 0;
- return -errno;
-}
-
-/*
- * scan a DHCPv4 OFFER to get its server-id option
- */
-
-int
-scan_for_srvid4(struct exchange *x, size_t cc)
-{
- size_t off = DHCP_OFF_OPTIONS;
-
- for (;;) {
- if (off + DHCP_OPTLEN_SRVID > cc) {
- fprintf(stderr, "truncated\n");
- return -1;
- }
- if (ibuf[off] == DHCP_OPT_DHCP_SRVID)
- break;
- if (ibuf[off] == DHCP_OPT_END) {
- fprintf(stderr, "server-id not found\n");
- return -1;
- }
- if (ibuf[off] == DHCP_OPT_PAD) {
- off++;
- continue;
- }
- off += 2 + ibuf[off + 1];
- }
- /* check length */
- if (ibuf[off + 1] != DHCP_OPTLEN_SRVID - 2) {
- fprintf(stderr,
- "bad server-id length (%hhu)\n",
- ibuf[off + 1]);
- return -1;
- }
- /* cache it in the global variables when required and not yet done */
- if ((usefirst != 0) && (gsrvid == NULL)) {
- memcpy(gsrvidbuf, ibuf + off, DHCP_OPTLEN_SRVID);
- gsrvid = gsrvidbuf;
- gsrvidlen = DHCP_OPTLEN_SRVID;
- }
- x->sid = ibuf + off;
- x->sidlen = DHCP_OPTLEN_SRVID;
- return 0;
-}
-
-/*
- * receive a DHCPv4 packet
- */
-
-void
-receive4(void)
-{
- struct exchange *x, *t;
- struct xlist *bucket;
- struct timespec now;
- ssize_t cc;
- uint32_t xid, hash;
- int checklost = 0;
- double delta;
-
- cc = recv(sock, ibuf, sizeof(ibuf), 0);
- if (cc < 0) {
- if ((errno == EAGAIN) ||
- (errno == EWOULDBLOCK) ||
- (errno == EINTR))
- return;
- perror("recv");
- fatal = 1;
- return;
- }
- /* enforce a reasonable length */
- if (cc < BOOTP_MIN_LEN) {
- tooshort++;
- return;
- }
- /* must be a BOOTP REPLY */
- if (ibuf[DHCP_OFF_OPCODE] != BOOTP_OP_REPLY)
- return;
- if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
- perror("clock_gettime(receive)");
- fatal = 1;
- return;
- }
- memcpy(&xid, ibuf + xid_discover4, 4);
- /* 4-packet exchange even/odd xid */
- if (simple == 0) {
- if ((xid & 1) != 0) {
- receive_reply(xid, &now);
- return;
- }
- hash = (xid >> 1) & (hashsize0 - 1);
- } else
- hash = xid & (hashsize0 - 1);
- /* now it is the second packet, get the bucket which is needed */
- bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
- /* try the 'next to be received' cache */
- if ((xnext0 != NULL) && (xnext0->xid == xid)) {
- x = xnext0;
- goto found;
- }
- /* if the rate is not illimited, garbage collect up to 3
- timed-out exchanges */
- if (rate != 0)
- checklost = 3;
- /* look for the exchange */
- ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
- double waited;
-
- if (x->xid == xid)
- goto found;
- if (checklost <= 0)
- continue;
- /* check for a timed-out exchange */
- waited = now.tv_sec - x->ts0.tv_sec;
- waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
- if (waited < losttime[0]) {
- checklost = 0;
- continue;
- }
- /* garbage collect timed-out exchange */
- checklost--;
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent0, x, gchain);
- free(x);
- collected[0] += 1;
- }
- /* no match? very late or not for us */
- orphans++;
- return;
-
- /* got it: update stats and move to the received queue */
- found:
- xrcount0++;
- x->ts1 = now;
- delta = x->ts1.tv_sec - x->ts0.tv_sec;
- delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
- if (delta < dmin0)
- dmin0 = delta;
- if (delta > dmax0)
- dmax0 = delta;
- dsum0 += delta;
- dsumsq0 += delta * delta;
- xnext0 = ISC_TAILQ_NEXT(x, gchain);
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent0, x, gchain);
- ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
- /* if the exchange is not finished, go to the second part */
- if (simple == 0) {
- int ret = 0;
-
- /* the server-ID option is needed */
- if ((usefirst != 0) && (gsrvid != NULL)) {
- x->sid = gsrvid;
- x->sidlen = gsrvidlen;
- } else
- ret = scan_for_srvid4(x, cc);
- if (ret >= 0)
- send_request4(x);
- }
-}
-
-/*
- * get the DHCPv6 socket descriptor
- */
-
-void
-getsock6(void)
-{
- struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &serveraddr;
- int ret;
-
- /* update local port */
- if (localport != 0) {
- uint16_t lp = htons((uint16_t) localport);
-
- ((struct sockaddr_in6 *) &localaddr)->sin6_port = lp;
- }
- sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
- if (sock < 0) {
- perror("socket");
- exit(1);
- }
- ret = bind(sock,
- (struct sockaddr *) &localaddr,
- sizeof(struct sockaddr_in6));
- if (ret < 0) {
- perror("Failed to bind v6 socket to local-link address");
- exit(1);
- }
- /* perform the multicast stuff when the destination is multicast */
- if (IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
- int hops = 1;
-
- ret = setsockopt(sock,
- IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
- &hops, sizeof(hops));
- if (ret < 0) {
- perror("setsockopt(IPV6_MULTICAST_HOPS)");
- exit(1);
- }
- }
- if (isinterface && IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
- unsigned idx = if_nametoindex(localname);
-
- if (idx == 0) {
- fprintf(stderr,
- "if_nametoindex(%s) failed\n",
- localname);
- exit(1);
- }
- ret = setsockopt(sock,
- IPPROTO_IPV6, IPV6_MULTICAST_IF,
- &idx, sizeof(idx));
- if (ret < 0) {
- perror("setsockopt(IPV6_MULTICAST_IF)");
- exit(1);
- }
- }
-}
-
-/*
- * build a DHCPv6 SOLICIT template
- * (implicit parameter is the DUID, don't assume an Ethernet link)
- */
-
-void
-build_template_solicit6(void)
-{
- uint8_t *p = template_solicit6;
-
- xid_solicit6 = DHCP6_OFF_XID;
- /* message type */
- p[DHCP6_OFF_MSGTYP] = DHCP6_OP_SOLICIT;
- /* options */
- p += DHCP6_OFF_OPTIONS;
- /* elapsed time */
- p[1] = DHCP6_OPT_ELAPSED_TIME;
- p[3] = 2;
- p += 6;
- /* rapid commit */
- if (rapidcommit != 0) {
- p[1] = DHCP6_OPT_RAPID_COMMIT;
- p += 4;
- }
- /* client ID */
- p[1] = DHCP6_OPT_CLIENTID;
- p[3] = duid_length;
- memcpy(p + 4, duid_prefix, duid_length);
- p += 4 + duid_length;
- random_solicit6 = p - template_solicit6;
- /* option request option */
- p[1] = DHCP6_OPT_ORO;
- p[3] = 4;
- p[5] = DHCP6_OPT_NAME_SERVERS;
- p[7] = DHCP6_OPT_DOMAIN_SEARCH;
- p += 8;
- /* IA_NA (IAID = 1, T1 = 3600, T2 = 5400) */
- p[1] = DHCP6_OPT_IA_NA;
- p[3] = 12;
- p[7] = 1;
- p[10] = 3600 >> 8;
- p[11] = 3600 & 0xff;
- p[14] = 5400 >> 8;
- p[15] = 5400 & 0xff;
- p += 16;
- /* set length */
- length_solicit6 = p - template_solicit6;
-}
-
-/*
- * get a DHCPv6 first packet (usually a SOLICIT) template
- * from the file given in the command line (-T<template-file>)
- * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
- */
-
-void
-get_template_solicit6(void)
-{
- uint8_t *p = template_solicit6;
- int fd, cc, i, j;
-
- fd = open(templatefile[0], O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "open(%s): %s\n",
- templatefile[0], strerror(errno));
- exit(2);
- }
- cc = read(fd, tbuf, sizeof(tbuf));
- (void) close(fd);
- if (cc < 0) {
- fprintf(stderr, "read(%s): %s\n",
- templatefile[0], strerror(errno));
- exit(1);
- }
- if (cc < 10) {
- fprintf(stderr, "file '%s' too short\n", templatefile[0]);
- exit(2);
- }
- if (cc > 8193) {
- fprintf(stderr,"file '%s' too large\n", templatefile[0]);
- exit(2);
- }
- j = 0;
- for (i = 0; i < cc; i++) {
- if (isspace((int) tbuf[i]))
- continue;
- if (!isxdigit((int) tbuf[i])) {
- fprintf(stderr,
- "illegal char[%d]='%c' in file '%s'\n",
- i, (int) tbuf[i], templatefile[0]);
- exit(2);
- }
- tbuf[j] = tbuf[i];
- j++;
- }
- cc = j;
- if ((cc & 1) != 0) {
- fprintf(stderr,
- "odd number of hexadecimal digits in file '%s'\n",
- templatefile[0]);
- exit(2);
- }
- length_solicit6 = cc >> 1;
- for (i = 0; i < cc; i += 2)
- (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
- if (xidoffset[0] >= 0)
- xid_solicit6 = (size_t) xidoffset[0];
- else
- xid_solicit6 = DHCP6_OFF_XID;
- if (xid_solicit6 + 3 > length_solicit6) {
- fprintf(stderr,
- "xid (at %zu) is outside the template (length %zu)?\n",
- xid_solicit6, length_solicit6);
- exit(2);
- }
- if (rndoffset[0] >= 0)
- random_solicit6 = (size_t) rndoffset[0];
- else
- random_solicit6 = 0;
- if (random_solicit6 > length_solicit6) {
- fprintf(stderr,
- "random (at %zu) outside the template (length %zu)?\n",
- random_solicit6, length_solicit6);
- exit(2);
- }
-}
-
-/*
- * build a DHCPv6 REQUEST template
- * (implicit parameter is the DUID, don't assume an Ethernet link)
- */
-
-void
-build_template_request6(void)
-{
- uint8_t *p = template_request6;
-
- xid_request6 = DHCP6_OFF_XID;
- /* message type */
- p[DHCP6_OFF_MSGTYP] = DHCP6_OP_REQUEST;
- /* options */
- p += DHCP6_OFF_OPTIONS;
- /* elapsed time */
- p[1] = DHCP6_OPT_ELAPSED_TIME;
- p[3] = 2;
- p += 4;
- elapsed_request6 = p - template_request6;
- p += 2;
- /* client ID */
- p[1] = DHCP6_OPT_CLIENTID;
- p[3] = duid_length;
- memcpy(p + 4, duid_prefix, duid_length);
- p += 4 + duid_length;
- random_request6 = p - template_request6;
- /* option request option */
- p[1] = DHCP6_OPT_ORO;
- p[3] = 4;
- p[5] = DHCP6_OPT_NAME_SERVERS;
- p[7] = DHCP6_OPT_DOMAIN_SEARCH;
- p += 8;
- /* server ID and IA_NA */
- serverid_request6 = p - template_request6;
- reqaddr_request6 = p - template_request6;
- /* set length */
- length_request6 = p - template_request6;
-}
-
-/*
- * get a DHCPv6 third packet (usually a REQUEST) template
- * from the file given in the command line (-T<template-file>)
- * and offsets (-X,-O,-E,-S,-I).
- */
-
-void
-get_template_request6(void)
-{
- uint8_t *p = template_request6;
- int fd, cc, i, j;
-
- fd = open(templatefile[1], O_RDONLY);
- if (fd < 0) {
- fprintf(stderr, "open(%s): %s\n",
- templatefile[1], strerror(errno));
- exit(2);
- }
- cc = read(fd, tbuf, sizeof(tbuf));
- (void) close(fd);
- if (cc < 0) {
- fprintf(stderr, "read(%s): %s\n",
- templatefile[1], strerror(errno));
- exit(1);
- }
- if (cc < 10) {
- fprintf(stderr, "file '%s' too short\n", templatefile[1]);
- exit(2);
- }
- if (cc > 8193) {
- fprintf(stderr,"file '%s' too large\n", templatefile[1]);
- exit(2);
- }
- j = 0;
- for (i = 0; i < cc; i++) {
- if (isspace((int) tbuf[i]))
- continue;
- if (!isxdigit((int) tbuf[i])) {
- fprintf(stderr,
- "illegal char[%d]='%c' in file '%s'\n",
- i, (int) tbuf[i], templatefile[1]);
- exit(2);
- }
- tbuf[j] = tbuf[i];
- j++;
- }
- cc = j;
- if ((cc & 1) != 0) {
- fprintf(stderr,
- "odd number of hexadecimal digits in file '%s'\n",
- templatefile[1]);
- exit(2);
- }
- length_request6 = cc >> 1;
- for (i = 0; i < cc; i += 2)
- (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
- if (xidoffset[1] >= 0)
- xid_request6 = (size_t) xidoffset[1];
- else
- xid_request6 = DHCP6_OFF_XID;
- if (xid_request6 + 3 > length_request6) {
- fprintf(stderr,
- "xid (at %zu) is outside the template (length %zu)?\n",
- xid_request6, length_request6);
- exit(2);
- }
- if (rndoffset[1] >= 0)
- random_request6 = (size_t) rndoffset[1];
- else
- random_request6 = 0;
- if (random_request6 > length_request6) {
- fprintf(stderr,
- "random (at %zu) outside the template (length %zu)?\n",
- random_request6, length_request6);
- exit(2);
- }
- if (elpoffset >= 0)
- elapsed_request6 = (size_t) elpoffset;
- if (elapsed_request6 + 2 > length_request6) {
- fprintf(stderr,
- "secs (at %zu) outside the template (length %zu)?\n",
- elapsed_request6, length_request6);
- exit(2);
- }
- serverid_request6 = (size_t) sidoffset;
- if (serverid_request6 > length_request6) {
- fprintf(stderr,
- "server-id option (at %zu) outside the template "
- "(length %zu)?\n",
- serverid_request6, length_request6);
- exit(2);
- }
- reqaddr_request6 = (size_t) ripoffset;
- if (reqaddr_request6 > length_request6) {
- fprintf(stderr,
- "requested-ip-address option (at %zu) outside "
- "the template (length %zu)?\n",
- reqaddr_request6, length_request6);
- exit(2);
- }
-}
-
-/*
- * send the DHCPv6 REQUEST third packet
- * (the transaction ID is odd)
- * (TODO: check for errors in the ADVERTISE)
- */
-
-void
-send_request6(struct exchange *x0)
-{
- struct exchange *x;
- struct xlist *bucket;
- size_t len;
- uint32_t hash;
- ssize_t ret;
-
- x = (struct exchange *) malloc(sizeof(*x));
- if (x == NULL) {
- locallimit++;
- perror("send2");
- return;
- }
-
- memcpy(x, x0, sizeof(*x));
- x->order2 = xscount2++;
- x->xid |= 1;
- hash = x->xid >> 1;
-
- ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
- hash &= hashsize2 - 1;
- bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
- ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
-
- len = length_request6;
- memcpy(obuf, template_request6, len);
- /* xid */
- obuf[xid_request6] = x->xid >> 16;
- obuf[xid_request6 + 1] = x->xid >> 8;
- obuf[xid_request6 + 2] = x->xid;
- /* random */
- randomize(random_request6, x->rnd);
- /* elapsed time */
- if (elapsed_request6 > 0) {
- int et;
-
- et = (x->ts1.tv_sec - x->ts0.tv_sec) * 100;
- et += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 10000000;
- if (et > 65535) {
- obuf[elapsed_request6] = 0xff;
- obuf[elapsed_request6 + 1] = 0xff;
- } else if (et > 0) {
- obuf[elapsed_request6] = et >> 8;
- obuf[elapsed_request6 + 1] = et & 0xff;
- }
- }
- /* server ID */
- if (serverid_request6 < length_request6)
- memmove(obuf + serverid_request6 + x->sidlen,
- obuf + serverid_request6,
- x->sidlen);
- memcpy(obuf + serverid_request6, x->sid, x->sidlen);
- len += x->sidlen;
- /* IA_NA */
- if (reqaddr_request6 < serverid_request6) {
- memmove(obuf + reqaddr_request6 + x->ianalen,
- obuf + reqaddr_request6,
- x->ianalen);
- memcpy(obuf + reqaddr_request6, x->iana, x->ianalen);
- } else if (reqaddr_request6 < length_request6) {
- memmove(obuf + reqaddr_request6 + x->sidlen + x->ianalen,
- obuf + reqaddr_request6 + x->sidlen,
- x->ianalen);
- memcpy(obuf + reqaddr_request6+ x->sidlen,
- x->iana,
- x->ianalen);
- } else
- memcpy(obuf + len, x->iana, x->ianalen);
- len += x->ianalen;
-
- /* timestamp */
- ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
- if (ret < 0) {
- perror("clock_gettime(send2)");
- fatal = 1;
- return;
- }
- ret = sendto(sock, obuf, len, 0,
- (struct sockaddr *) &serveraddr,
- sizeof(struct sockaddr_in6));
- if (ret >= 0)
- return;
- if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
- (errno == ENOBUFS) || (errno == ENOMEM))
- locallimit++;
- perror("send2");
-}
-
-/*
- * send the DHCPv6 SOLICIT first packet
- * (for 4-exchange, the transaction ID xid is even)
- */
-
-int
-send6(void)
-{
- struct exchange *x;
- struct xlist *bucket;
- uint32_t hash;
- ssize_t ret;
-
- x = (struct exchange *) malloc(sizeof(*x));
- if (x == NULL)
- return -ENOMEM;
-
- memset(x, 0, sizeof(*x));
- x->order0 = xscount0++;
- hash = x->rnd = (uint32_t) random();
- if (simple == 0)
- x->xid = (hash << 1) & 0x00ffffff;
- else
- x->xid = hash & 0x00ffffff;
-
- ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
- hash &= hashsize0 - 1;
- bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
- ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
-
- memcpy(obuf, template_solicit6, length_solicit6);
- /* xid */
- obuf[xid_solicit6] = x->xid >> 16;
- obuf[xid_solicit6 + 1] = x->xid >> 8;
- obuf[xid_solicit6 + 2] = x->xid;
- /* random */
- x->rnd = randomize(random_solicit6, x->rnd);
-
- /* timestamp */
- ret = clock_gettime(CLOCK_REALTIME, &last);
- if (ret < 0) {
- perror("clock_gettime(send)");
- fatal = 1;
- return -errno;
- }
- x->ts0 = last;
- errno = 0;
- ret = sendto(sock, obuf, length_solicit6, 0,
- (struct sockaddr *) &serveraddr,
- sizeof(struct sockaddr_in6));
- if (ret == (ssize_t) length_solicit6)
- return 0;
- return -errno;
-}
-
-/*
- * scan a DHCPv6 ADVERTISE to get its server-id option
- */
-
-int
-scan_for_srvid6(struct exchange *x, size_t cc)
-{
- size_t off = DHCP6_OFF_OPTIONS, len;
-
- for (;;) {
- if (off + 4 > cc) {
- fprintf(stderr, "server-id not found\n");
- return -1;
- }
- if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_SERVERID))
- break;
- off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
- }
- len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
- /* cache it in the global variables when required and not yet done */
- if ((usefirst != 0) && (gsrvid == NULL)) {
- if (len <= sizeof(gsrvidbuf)) {
- memcpy(gsrvidbuf, ibuf + off, len);
- gsrvid = gsrvidbuf;
- gsrvidlen = len;
- } else {
- gsrvid = (uint8_t *) malloc(len);
- if (gsrvid == NULL) {
- perror("malloc(gsrvid");
- return -1;
- }
- memcpy(gsrvid, ibuf + off, len);
- gsrvidlen = len;
- }
- }
- x->sid = ibuf + off;
- x->sidlen = len;
- return 0;
-}
-
-/*
- * scan a DHCPv6 ADVERTISE to get its IA_NA option
- * (TODO: check for errors)
- */
-
-int
-scan_for_ia_na(struct exchange *x, size_t cc)
-{
- size_t off = DHCP6_OFF_OPTIONS, len;
-
- for (;;) {
- if (off + 4 > cc) {
- fprintf(stderr, "ia-na not found\n");
- return -1;
- }
- if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_IA_NA))
- break;
- off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
- }
- len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
- x->iana = ibuf + off;
- x->ianalen = len;
- return 0;
-}
-
-/*
- * receive a DHCPv6 packet
- */
-
-void
-receive6(void)
-{
- struct exchange *x, *t;
- struct xlist *bucket;
- struct timespec now;
- ssize_t cc;
- uint32_t xid, hash;
- int checklost = 0;
- double delta;
-
- cc = recv(sock, ibuf, sizeof(ibuf), 0);
- if (cc < 0) {
- if ((errno == EAGAIN) ||
- (errno == EWOULDBLOCK) ||
- (errno == EINTR))
- return;
- perror("recv");
- fatal = 1;
- return;
- }
- /* enforce a reasonable length */
- if (cc < 22) {
- tooshort++;
- return;
- }
- if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
- perror("clock_gettime(receive)");
- fatal = 1;
- return;
- }
- xid = ibuf[xid_solicit6] << 16;
- xid |= ibuf[xid_solicit6 + 1] << 8;
- xid |= ibuf[xid_solicit6 + 2];
- /* 4-packet exchange even/odd xid */
- if (simple == 0) {
- if ((xid & 1) != 0) {
- receive_reply(xid, &now);
- return;
- }
- hash = (xid >> 1) & (hashsize0 - 1);
- } else
- hash = xid & (hashsize0 - 1);
- /* now it is the second packet, get the bucket which is needed */
- bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
- /* try the 'next to be received' cache */
- if ((xnext0 != NULL) && (xnext0->xid == xid)) {
- x = xnext0;
- goto found;
- }
- /* if the rate is not illimited, garbage collect up to 3
- timed-out exchanges */
- if (rate != 0)
- checklost = 3;
- /* look for the exchange */
- ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
- double waited;
-
- if (x->xid == xid)
- goto found;
- if (checklost <= 0)
- continue;
- /* check for a timed-out exchange */
- waited = now.tv_sec - x->ts0.tv_sec;
- waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
- if (waited < losttime[0]) {
- checklost = 0;
- continue;
- }
- /* garbage collect timed-out exchange */
- checklost--;
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent0, x, gchain);
- free(x);
- collected[0] += 1;
- }
- /* no match? very late or not for us */
- orphans++;
- return;
-
- /* got it: update stats and move to the received queue */
- found:
- xrcount0++;
- x->ts1 = now;
- delta = x->ts1.tv_sec - x->ts0.tv_sec;
- delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
- if (delta < dmin0)
- dmin0 = delta;
- if (delta > dmax0)
- dmax0 = delta;
- dsum0 += delta;
- dsumsq0 += delta * delta;
- xnext0 = ISC_TAILQ_NEXT(x, gchain);
- ISC_TAILQ_REMOVE(bucket, x, hchain);
- ISC_TAILQ_REMOVE(&xsent0, x, gchain);
- ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
- /* if the exchange is not finished, go to the second part */
- if (simple == 0) {
- int ret = 0;
-
- /* the server-ID option is needed */
- if ((usefirst != 0) && (gsrvid != NULL)) {
- x->sid = gsrvid;
- x->sidlen = gsrvidlen;
- } else
- ret = scan_for_srvid6(x, cc);
- /* and the IA_NA option too */
- if (ret >= 0)
- ret = scan_for_ia_na(x, cc);
- if (ret >= 0)
- send_request6(x);
- }
-}
-
-/*
- * decode a base command line parameter
- * (currently only MAC address and DUID are supported)
- */
-
-void
-decodebase(void)
-{
- char *b0 = base[basecnt];
-
- /* MAC address (alias Ethernet address) */
- if ((strncasecmp(b0, "mac=", 4) == 0) ||
- (strncasecmp(b0, "ether=", 6) == 0)) {
- char *b;
-
- b = strchr(b0, '=') + 1;
- if (sscanf(b, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
- &mac_prefix[0], &mac_prefix[1],
- &mac_prefix[2], &mac_prefix[3],
- &mac_prefix[4], &mac_prefix[5]) == 6) {
- if ((diags != NULL) && (strchr(diags, 'a') != NULL))
- printf("set MAC to %s\n", b);
- return;
- }
- fprintf(stderr,
- "expected base in '%*s=xx:xx:xx:xx:xx:xx' format\n",
- (int) (b - b0 - 1), b0);
- exit(2);
- }
- /* DUID */
- if (strncasecmp(b0, "duid=", 5) == 0) {
- char *b;
- size_t i, l;
-
- if (duid_prefix != NULL) {
- fprintf(stderr, "duid was already set?\n");
- exit(2);
- }
- b = strchr(b0, '=') + 1;
- l = 0;
- while (*b != '\0') {
- if (!isxdigit((int) *b)) {
- fprintf(stderr,
- "illegal char '%c' in duid\n",
- (int) *b);
- exit(2);
- }
- b++;
- l++;
- }
- if ((l & 1) != 0) {
- fprintf(stderr,
- "odd number of hexadecimal digits in duid\n");
- exit(2);
- }
- l /= 2;
- if (l > 64) {
- fprintf(stderr, "duid too large\n");
- exit(2);
- }
- duid_prefix = (uint8_t *) malloc(l);
- if (duid_prefix == NULL) {
- perror("malloc(duid)");
- exit(1);
- }
- b = strchr(b0, '=') + 1;
- for (i = 0; i < l; i++, b += 2)
- (void) sscanf(b, "%02hhx", &duid_prefix[i]);
- duid_length = l;
- if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
- b = strchr(b0, '=') + 1;
- printf("set DUID[%d] to %s\n",
- duid_length, b);
- }
- return;
- }
- /* other */
- fprintf(stderr, "not yet supported base '%s'\n", b0);
- exit(2);
-}
-
-/*
- * get the server socket address from the command line:
- * - flags: inherited from main, 0 or AI_NUMERICHOST (for literals)
- */
-
-void
-getserveraddr(const int flags)
-{
- struct addrinfo hints, *res;
- char *service;
- int ret;
-
- memset(&hints, 0, sizeof(hints));
- if (ipversion == 4) {
- hints.ai_family = AF_INET;
- service = "67";
- } else {
- hints.ai_family = AF_INET6;
- service = "547";
- }
- hints.ai_socktype = SOCK_DGRAM;
-
- hints.ai_flags = AI_NUMERICSERV | flags;
-#if defined(AI_ADDRCONFIG)
- hints.ai_flags |= AI_ADDRCONFIG;
-#endif
- hints.ai_protocol = IPPROTO_UDP;
-
- ret = getaddrinfo(servername, service, &hints, &res);
- if (ret != 0) {
- fprintf(stderr, "bad server=%s: %s\n",
- servername, gai_strerror(ret));
- exit(2);
- }
- if (res->ai_next != NULL) {
- fprintf(stderr, "ambiguous server=%s\n", servername);
- exit(2);
- }
- memcpy(&serveraddr, res->ai_addr, res->ai_addrlen);
- freeaddrinfo(res);
-}
-
-/*
- * get the interface from the command line:
- * - name of the interface
- * - socket address to fill
- * (in IPv6, get the first link-local address)
- */
-
-void
-getinterface(const char *name, struct sockaddr_storage *ss)
-{
- struct ifaddrs *ifaddr, *ifa;
- int ret;
- size_t len;
-
- ret = getifaddrs(&ifaddr);
- if (ret < 0) {
- perror("getifaddrs");
- exit(1);
- }
-
- for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
- if (ifa->ifa_name == NULL)
- continue;
- if (strcmp(ifa->ifa_name, name) != 0)
- continue;
- if ((ifa->ifa_flags & IFF_UP) == 0)
- continue;
- if ((ifa->ifa_flags & IFF_MULTICAST) == 0)
- continue;
- if (ifa->ifa_addr == NULL)
- continue;
- if ((ipversion == 4) &&
- (ifa->ifa_addr->sa_family != AF_INET))
- continue;
- if (ipversion == 6) {
- struct sockaddr_in6 *a6;
-
- a6 = (struct sockaddr_in6 *) ifa->ifa_addr;
- if (a6->sin6_family != AF_INET6)
- continue;
- if (!IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr))
- continue;
- }
- if (ipversion == 4)
- len = sizeof(struct sockaddr_in);
- else
- len = sizeof(struct sockaddr_in6);
- memcpy(ss, ifa->ifa_addr, len);
- /* fill the server port */
- if (ipversion == 4)
- ((struct sockaddr_in *) ss)->sin_port = htons(67);
- else
- ((struct sockaddr_in6 *) ss)->sin6_port = htons(546);
- return;
- }
- fprintf(stderr, "can't find interface %s\n", name);
- exit(1);
-}
-
-/*
- * get the local socket address from the command line
- * (if it doesn't work, try an interface name)
- */
-
-void
-getlocaladdr(void)
-{
- struct addrinfo hints, *res;
- char *service;
- int ret;
-
- memset(&hints, 0, sizeof(hints));
- if (ipversion == 4) {
- hints.ai_family = AF_INET;
- service = "67";
- } else {
- hints.ai_family = AF_INET6;
- service = "546";
- }
- hints.ai_socktype = SOCK_DGRAM;
- hints.ai_flags = AI_NUMERICSERV;
-#if defined(AI_ADDRCONFIG)
- hints.ai_flags |= AI_ADDRCONFIG;
-#endif
- hints.ai_protocol = IPPROTO_UDP;
-
- ret = getaddrinfo(localname, service, &hints, &res);
- if ((ret == EAI_NONAME)
-#ifdef EAI_NODATA
- || (ret == EAI_NODATA)
-#endif
- ) {
- isinterface = 1;
- getinterface(localname, &localaddr);
- return;
- }
- if (ret != 0) {
- fprintf(stderr,
- "bad -l<local-addr=%s>: %s\n",
- localname,
- gai_strerror(ret));
- exit(2);
- }
- /* refuse multiple addresses */
- if (res->ai_next != NULL) {
- fprintf(stderr,
- "ambiguous -l<local-addr=%s>\n",
- localname);
- exit(2);
- }
- memcpy(&localaddr, res->ai_addr, res->ai_addrlen);
- freeaddrinfo(res);
- return;
-}
-
-/*
- * get the local socket address from the server one
- */
-
-void
-getlocal(void)
-{
- int ret, s;
- socklen_t l;
-
- if (ipversion == 4) {
- l = sizeof(struct sockaddr_in);
- s = socket(PF_INET, SOCK_DGRAM, 0);
- } else {
- l = sizeof(struct sockaddr_in6);
- s = socket(PF_INET6, SOCK_DGRAM, 0);
- }
- if (s < 0) {
- perror("socket");
- exit(1);
- }
- if ((ipversion == 4) && (isbroadcast != 0)) {
- int on = 1;
-
- ret = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
- if (ret < 0) {
- perror("setsockopt(SO_BROADCAST)");
- exit(1);
- }
- }
- ret = connect(s, (struct sockaddr *) &serveraddr, l);
- if (ret < 0) {
- perror("connect");
- exit(1);
- }
- l = sizeof(localaddr);
- ret = getsockname(s, (struct sockaddr *) &localaddr, &l);
- if (ret < 0) {
- perror("getsockname");
- exit(1);
- }
- (void) close(s);
- /* fill the local port */
- if (ipversion == 4)
- ((struct sockaddr_in *) &localaddr)->sin_port = htons(67);
- else
- ((struct sockaddr_in6 *) &localaddr)->sin6_port = htons(546);
-}
-
-/*
- * intermediate reporting
- * (note: an in-transit packet can be reported as dropped)
- */
-
-void
-reporting(void)
-{
- dreport.tv_sec += report;
-
- if (xscount2 == 0) {
- printf("sent: %llu, received: %llu (drops: %lld)",
- (unsigned long long) xscount0,
- (unsigned long long) xrcount0,
- (long long) (xscount0 - xrcount0));
- if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
- double avg;
-
- avg = dsum0 / xrcount0;
- printf(" average: %.3f ms", avg * 1e3);
- }
- } else {
- printf("sent: %llu/%llu received: %llu/%llu "
- "(drops: %lld/%lld)",
- (unsigned long long) xscount0,
- (unsigned long long) xscount2,
- (unsigned long long) xrcount0,
- (unsigned long long) xrcount2,
- (long long) (xscount0 - xrcount0),
- (long long) (xscount2 - xrcount2));
- if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
- double avg0, avg2;
-
- avg0 = dsum0 / xrcount0;
- if (xrcount2 != 0)
- avg2 = dsum2 / xrcount2;
- else
- avg2 = 0.;
- printf(" average: %.3f/%.3f ms",
- avg0 * 1e3, avg2 * 1e3);
- }
- }
- printf("\n");
-}
-
-/*
- * SIGCHLD handler
- */
-
-void
-reapchild(int sig)
-{
- // Do something with variables to avoid unused-variable warning
- int status;
- status = sig;
- status = 0;
- while (wait3(&status, WNOHANG, NULL) > 0)
- /* continue */;
-}
-
-/*
- * SIGINT handler
- */
-
-void
-interrupt(int sig)
-{
- // Do something to avoid unused-variable warning
- int s = sig;
- s = 1;
- interrupted = s;
-}
-
-/*
- * '-v' handler
- */
-
-void
-version(void)
-{
- fprintf(stderr, "version 0.01\n");
-}
-
-/*
- * usage (from the wiki)
- */
-
-void
-usage(void)
-{
- fprintf(stderr, "%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"
-"\f\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");
-}
-
-/*
- * main function / entry point
- */
-
-int
-main(const int argc, char * const argv[])
-{
- int opt, flags = 0, ret, i;
- long long r;
- char *pc;
- extern char *optarg;
- extern int optind;
-
-#define OPTIONS "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:"
-
- /* decode options */
- while ((opt = getopt(argc, argv, OPTIONS)) != -1)
- switch (opt) {
- case 'h':
- usage();
- exit(0);
-
- case 'v':
- version();
- exit(0);
-
- case '4':
- if (ipversion == 6) {
- fprintf(stderr, "IP version already set to 6\n");
- usage();
- exit(2);
- }
- ipversion = 4;
- break;
-
- case '6':
- if (ipversion == 4) {
- fprintf(stderr, "IP version already set to 4\n");
- usage();
- exit(2);
- }
- ipversion = 6;
- break;
-
- case 'r':
- rate = atoi(optarg);
- if (rate <= 0) {
- fprintf(stderr, "rate must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 't':
- report = atoi(optarg);
- if (report <= 0) {
- fprintf(stderr, "report must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'R':
- r = atoll(optarg);
- if (r < 0) {
- fprintf(stderr,
- "range must not be a negative integer\n");
- usage();
- exit(2);
- }
- range = (uint32_t) r;
- if ((range != 0) && (range != UINT32_MAX)) {
- uint32_t s = range + 1;
- uint64_t b = UINT32_MAX + 1, m;
-
- m = (b / s) * s;
- if (m == b)
- maxrandom = 0;
- else
- maxrandom = (uint32_t) m;
- }
- break;
-
- case 'b':
- if (basecnt > 3) {
- fprintf(stderr, "too many bases\n");
- usage();
- exit(2);
- }
- base[basecnt] = optarg;
- decodebase();
- basecnt++;
- break;
-
- case 'n':
- numreq[gotnumreq] = atoi(optarg);
- if (numreq[gotnumreq] <= 0) {
- fprintf(stderr,
- "num-request must be a positive integer\n");
- usage();
- exit(2);
- }
- gotnumreq = 1;
- break;
-
- case 'p':
- period = atoi(optarg);
- if (period <= 0) {
- fprintf(stderr,
- "test-period must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'd':
- losttime[gotlosttime] = atof(optarg);
- if (losttime[gotlosttime] <= 0.) {
- fprintf(stderr,
- "drop-time must be a positive number\n");
- usage();
- exit(2);
- }
- gotlosttime = 1;
- break;
-
- case 'D':
- pc = strchr(optarg, '%');
- if (pc != NULL) {
- *pc = '\0';
- maxpdrop[gotmaxdrop] = atof(optarg);
- if ((maxpdrop[gotmaxdrop] <= 0) ||
- (maxpdrop[gotmaxdrop] >= 100)) {
- fprintf(stderr,
- "invalid drop-time percentage\n");
- usage();
- exit(2);
- }
- gotmaxdrop = 1;
- break;
- }
- maxdrop[gotmaxdrop] = atoi(optarg);
- if (maxdrop[gotmaxdrop] <= 0) {
- fprintf(stderr,
- "max-drop must be a positive integer\n");
- usage();
- exit(2);
- }
- gotmaxdrop = 1;
- break;
-
- case 'l':
- localname = optarg;
- break;
-
- case 'P':
- preload = atoi(optarg);
- if (preload < 0) {
- fprintf(stderr,
- "preload must not be a negative integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'a':
- aggressivity = atoi(optarg);
- if (aggressivity <= 0) {
- fprintf(stderr,
- "aggressivity must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'L':
- localport = atoi(optarg);
- if (localport < 0) {
- fprintf(stderr,
- "local-port must not be a negative integer\n");
- usage();
- exit(2);
- }
- if (localport > (int) UINT16_MAX) {
- fprintf(stderr,
- "local-port must be lower than %d\n",
- (int) UINT16_MAX);
- usage();
- exit(2);
- }
- break;
-
- case 's':
- seeded = 1;
- seed = (unsigned int) atol(optarg);
- break;
-
- case 'i':
- simple = 1;
- break;
-
- case 'B':
- isbroadcast = 1;
- break;
-
- case 'c':
- rapidcommit = 1;
- break;
-
- case '1':
- usefirst = 1;
- break;
-
- case 'T':
- if (templatefile[0] != NULL) {
- if (templatefile[1] != NULL) {
- fprintf(stderr,
- "template-files are already set\n");
- usage();
- exit(2);
- }
- templatefile[1] = optarg;
- } else
- templatefile[0] = optarg;
- break;
-
- case 'X':
- if (xidoffset[0] >= 0)
- i = 1;
- else
- i = 0;
- xidoffset[i] = atoi(optarg);
- if (xidoffset[i] <= 0) {
- fprintf(stderr,
- "xid-offset must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'O':
- if (rndoffset[0] >= 0)
- i = 1;
- else
- i = 0;
- rndoffset[i] = atoi(optarg);
- if (rndoffset[i] < 3) {
- fprintf(stderr,
- "random-offset must be greater than 3\n");
- usage();
- exit(2);
- }
- break;
-
- case 'E':
- elpoffset = atoi(optarg);
- if (elpoffset < 0) {
- fprintf(stderr,
- "time-offset must not be a "
- "negative integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'S':
- sidoffset = atoi(optarg);
- if (sidoffset <= 0) {
- fprintf(stderr,
- "srvid-offset must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'I':
- ripoffset = atoi(optarg);
- if (ripoffset <= 0) {
- fprintf(stderr,
- "ip-offset must be a positive integer\n");
- usage();
- exit(2);
- }
- break;
-
- case 'x':
- diags = optarg;
- break;
-
- case 'w':
- wrapped = optarg;
- break;
-
- default:
- usage();
- exit(2);
- }
-
- /* adjust some global variables */
- if (ipversion == 0)
- ipversion = 4;
- if (templatefile[1] != NULL) {
- if (xidoffset[1] < 0)
- xidoffset[1] = xidoffset[0];
- if (rndoffset[1] < 0)
- rndoffset[1] = rndoffset[0];
- }
-
- /* when required, print the internal view of the command line */
- if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
- printf("IPv%d", ipversion);
- if (simple != 0) {
- if (ipversion == 4)
- printf(" DO only");
- else
- printf(" SA only");
- }
- if (rate != 0)
- printf(" rate=%d", rate);
- if (report != 0)
- printf(" report=%d", report);
- if (range != 0) {
- if (strchr(diags, 'r') != NULL)
- printf(" range=0..%d [0x%x]",
- range,
- (unsigned int) maxrandom);
- else
- printf(" range=0..%d", range);
- }
- if (basecnt != 0)
- for (i = 0; i < basecnt; i++)
- printf(" base[%d]='%s'", i, base[i]);
- if (gotnumreq != 0)
- printf(" num-request=%d,%d", numreq[0], numreq[1]);
- if (period != 0)
- printf(" test-period=%d", period);
- printf(" drop-time=%g,%g", losttime[0], losttime[1]);
- if ((maxdrop[0] != 0) || (maxdrop[1] != 0))
- printf(" max-drop=%d,%d", maxdrop[0], maxdrop[1]);
- if ((maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))
- printf(" max-drop=%2.2f%%,%2.2f%%",
- maxpdrop[0], maxpdrop[1]);
- if (preload != 0)
- printf(" preload=%d", preload);
- printf(" aggressivity=%d", aggressivity);
- if (localport != 0)
- printf(" local-port=%d", localport);
- if (seeded)
- printf(" seed=%u", seed);
- if (isbroadcast != 0)
- printf(" broadcast");
- if (rapidcommit != 0)
- printf(" rapid-commit");
- if (usefirst != 0)
- printf(" use-first");
- if ((templatefile[0] != NULL) && (templatefile[1] == NULL))
- printf(" template-file='%s'", templatefile[0]);
- else if (templatefile[1] != NULL)
- printf(" template-file='%s','%s'",
- templatefile[0], templatefile[1]);
- if ((xidoffset[0] >= 0) && (xidoffset[1] < 0))
- printf(" xid-offset=%d", xidoffset[0]);
- else if (xidoffset[1] >= 0)
- printf(" xid-offset=%d,%d",
- xidoffset[0], xidoffset[1]);
- if ((rndoffset[0] >= 0) && (rndoffset[1] < 0))
- printf(" xid-offset=%d", rndoffset[0]);
- else if (rndoffset[1] >= 0)
- printf(" xid-offset=%d,%d",
- rndoffset[0], rndoffset[1]);
- if (elpoffset >= 0)
- printf(" time-offset=%d", elpoffset);
- if (sidoffset >= 0)
- printf(" srvid-offset=%d", sidoffset);
- if (ripoffset >= 0)
- printf(" ip-offset=%d", ripoffset);
- printf(" diagnotic-selectors='%s'", diags);
- if (wrapped != NULL)
- printf(" wrapped='%s'", wrapped);
- printf("\n");
- }
-
- /* check DHCPv4 only options */
- if ((ipversion != 4) && (isbroadcast != 0)) {
- fprintf(stderr, "-b is not compatible with IPv6 (-6)\n");
- usage();
- exit(2);
- }
-
- /* check DHCPv6 only options */
- if ((ipversion != 6) && (rapidcommit != 0)) {
- fprintf(stderr, "-6 (IPv6) must be set to use -c\n");
- usage();
- exit(2);
- }
-
- /* check 4-packet (aka not simple) mode options */
- if ((simple != 0) && (numreq[1] != 0)) {
- fprintf(stderr,
- "second -n<num-request> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (losttime[1] != 1.)) {
- fprintf(stderr,
- "second -d<drop-time> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) &&
- ((maxdrop[1] != 0) || (maxpdrop[1] != 0.))) {
- fprintf(stderr,
- "second -D<max-drop> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (usefirst != 0)) {
- fprintf(stderr,
- "-1 is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (templatefile[1] != NULL)) {
- fprintf(stderr,
- "second -T<template-file> is not "
- "compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (xidoffset[1] >= 0)) {
- fprintf(stderr,
- "second -X<xid-offset> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (rndoffset[1] >= 0)) {
- fprintf(stderr,
- "second -O<random-offset is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (elpoffset >= 0)) {
- fprintf(stderr,
- "-E<time-offset> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (sidoffset >= 0)) {
- fprintf(stderr,
- "-S<srvid-offset> is not compatible with -i\n");
- usage();
- exit(2);
- }
- if ((simple != 0) && (ripoffset >= 0)) {
- fprintf(stderr,
- "-I<ip-offset> is not compatible with -i\n");
- usage();
- exit(2);
- }
-
-
- /* check simple mode options */
- if ((simple == 0) && (rapidcommit != 0)) {
- fprintf(stderr, "-i must be set to use -c\n");
- usage();
- exit(2);
- }
-
- /* check rate '-r' options */
- if ((rate == 0) && (report != 0)) {
- fprintf(stderr,
- "-r<rate> must be set to use -t<report>\n");
- usage();
- exit(2);
- }
- if ((rate == 0) && ((numreq[0] != 0) || (numreq[1] != 0))) {
- fprintf(stderr,
- "-r<rate> must be set to use -n<num-request>\n");
- usage();
- exit(2);
- }
- if ((rate == 0) && (period != 0)) {
- fprintf(stderr,
- "-r<rate> must be set to use -p<test-period>\n");
- usage();
- exit(2);
- }
- if ((rate == 0) &&
- ((maxdrop[0] != 0) || (maxdrop[1] != 0) ||
- (maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))) {
- fprintf(stderr,
- "-r<rate> must be set to use -D<max-drop>\n");
- usage();
- exit(2);
- }
-
- /* check (first) template file options */
- if ((templatefile[0] == NULL) && (xidoffset[0] >= 0)) {
- fprintf(stderr,
- "-T<template-file> must be set to "
- "use -X<xid-offset>\n");
- usage();
- exit(2);
- }
- if ((templatefile[0] == NULL) && (rndoffset[0] >= 0)) {
- fprintf(stderr,
- "-T<template-file> must be set to "
- "use -O<random-offset>\n");
- usage();
- exit(2);
- }
-
- /* check (second) template file options */
- if ((templatefile[1] == NULL) && (elpoffset >= 0)) {
- fprintf(stderr,
- "second/request -T<template-file> must be set to "
- "use -E<time-offset>\n");
- usage();
- exit(2);
- }
- if ((templatefile[1] == NULL) && (sidoffset >= 0)) {
- fprintf(stderr,
- "second/request -T<template-file> must be set to "
- "use -S<srvid-offset>\n");
- usage();
- exit(2);
- }
- if ((templatefile[1] == NULL) && (ripoffset >= 0)) {
- fprintf(stderr,
- "second/request -T<template-file> must be set to "
- "use -I<ip-offset>\n");
- usage();
- exit(2);
- }
-
- /* check various template file(s) and other condition(s) options */
- if ((templatefile[0] != NULL) && (range > 0) && (rndoffset[0] < 0)) {
- fprintf(stderr,
- "-O<random-offset> must be set when "
- "-T<template-file> and -R<range> are used\n");
- usage();
- exit(2);
- }
- if ((templatefile[1] != NULL) && (sidoffset < 0)) {
- fprintf(stderr,
- "-S<srvid-offset> must be set when second "
- "-T<template-file> is used\n");
- usage();
- exit(2);
- }
- if ((templatefile[1] != NULL) && (ripoffset < 0)) {
- fprintf(stderr,
- "-I<ip-offset> must be set when second "
- "-T<template-file> is used\n");
- usage();
- exit(2);
- }
-
- /* get the server argument */
- if (optind < argc - 1) {
- fprintf(stderr, "extra arguments?\n");
- usage();
- exit(2);
- }
- if (optind == argc - 1) {
- servername = argv[optind];
- /* decode special cases */
- if ((ipversion == 4) &&
- (strcmp(servername, "all") == 0)) {
- flags = AI_NUMERICHOST;
- isbroadcast = 1;
- servername = "255.255.255.255";
- } else if ((ipversion == 6) &&
- (strcmp(servername, "all") == 0)) {
- flags = AI_NUMERICHOST;
- servername = "FF02::1:2";
- } else if ((ipversion == 6) &&
- (strcmp(servername, "servers") == 0)) {
- flags = AI_NUMERICHOST;
- servername = "FF05::1:3";
- }
- }
-
- /* handle the local '-l' address/interface */
- if (localname != NULL) {
- /* given */
- getlocaladdr();
- if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
- if (isinterface)
- printf("interface='%s'\n", localname);
- else
- printf("local-addr='%s'\n", localname);
- }
- /* get the not given server from it */
- if (servername == NULL) {
- if (isinterface && (ipversion == 4)) {
- flags = AI_NUMERICHOST;
- isbroadcast = 1;
- servername = "255.255.255.255";
- } else if (isinterface && (ipversion == 6)) {
- flags = AI_NUMERICHOST;
- servername = "FF02::1:2";
- } else {
- fprintf(stderr,
- "without an interface "
- "server is required\n");
- usage();
- exit(2);
- }
- }
- } else if (servername == NULL) {
- fprintf(stderr, "without an interface server is required\n");
- usage();
- exit(2);
- }
- /* get the server socket address */
- getserveraddr(flags);
- /* finish local/server socket address stuff and print it */
- if ((diags != NULL) && (strchr(diags, 'a') != NULL))
- printf("server='%s'\n", servername);
- if (localname == NULL)
- getlocal();
- if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
- char addr[NI_MAXHOST];
-
- ret = getnameinfo((struct sockaddr *) &localaddr,
- sizeof(localaddr),
- addr,
- NI_MAXHOST,
- NULL,
- 0,
- NI_NUMERICHOST);
- if (ret != 0) {
- fprintf(stderr,
- "can't get the local address: %s\n",
- gai_strerror(ret));
- exit(1);
- }
- printf("local address='%s'\n", addr);
- }
-
- /* initialize exchange structures */
- inits();
-
- /* get the socket descriptor and template(s) */
- if (ipversion == 4) {
- getsock4();
- if (templatefile[0] == NULL)
- build_template_discover4();
- else
- get_template_discover4();
- if (simple == 0) {
- if (templatefile[1] == NULL)
- build_template_request4();
- else
- get_template_request4();
- }
- } else {
- getsock6();
- if (duid_prefix != NULL) {
- if (templatefile[0] == NULL)
- build_template_solicit6();
- else
- get_template_solicit6();
- if (simple == 0) {
- if (templatefile[1] == NULL)
- build_template_request6();
- else
- get_template_request6();
- }
- }
- }
- /* sanity check */
- if ((unsigned) sock > FD_SETSIZE) {
- fprintf(stderr, "socket descriptor (%d) too large?!\n", sock);
- exit(1);
- }
- /* make the socket descriptor not blocking */
- flags = fcntl(sock, F_GETFL, 0);
- if (flags < 0) {
- perror("fcntl(F_GETFL)");
- exit(1);
- }
- if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
- perror("fcntl(F_SETFL)");
- exit(1);
- }
-
- /* wrapped start */
- if (wrapped != NULL) {
- pid_t pid;
-
- (void) signal(SIGCHLD, reapchild);
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- } else if (pid == 0)
- (void) execlp(wrapped, "start", (char *) NULL);
- }
-
- /* boot is done! */
- if (clock_gettime(CLOCK_REALTIME, &boot) < 0) {
- perror("clock_gettime(boot)");
- exit(1);
- }
-
- /* compute the next intermediate reporting date */
- if (report != 0) {
- dreport.tv_sec = boot.tv_sec + report;
- dreport.tv_nsec = boot.tv_nsec;
- }
-
- /* compute the DUID (the current date is needed) */
- if ((ipversion == 6) && (duid_prefix == NULL)) {
- uint32_t curdate;
-
- duid_length = 14;
- duid_prefix = (uint8_t *) malloc(duid_length);
- if (duid_prefix == NULL) {
- perror("malloc(duid)");
- exit(1);
- }
- duid_prefix[0] = DHCP6_DUID_LLT >> 8;
- duid_prefix[1] = DHCP6_DUID_LLT;
- duid_prefix[2] = DHCP_HTYPE_ETHER >> 8;
- duid_prefix[3] = DHCP_HTYPE_ETHER;
- curdate = htonl(boot.tv_sec - DHCP6_DUID_EPOCH);
- memcpy(duid_prefix + 4, &curdate, 4);
- memcpy(duid_prefix + 8, mac_prefix, 6);
- /* the DUID is in template(s) */
- if (templatefile[0] == NULL)
- build_template_solicit6();
- else
- get_template_solicit6();
- if (simple == 0) {
- if (templatefile[1] == NULL)
- build_template_request6();
- else
- get_template_request6();
- }
- }
-
- /* seed the random generator */
- if (seeded == 0)
- seed = (unsigned int) (boot.tv_sec + boot.tv_nsec);
- srandom(seed);
-
- /* preload the server with at least one packet */
- compsend = preload + 1;
- for (i = 0; i <= preload; i++) {
- if (ipversion == 4)
- ret = send4();
- else
- ret = send6();
- if (ret < 0) {
- /* failure at the first packet is fatal */
- if (i == 0) {
- fprintf(stderr,
- "initial send failed: %s\n",
- strerror(-ret));
- exit(1);
- }
- if ((errno == EAGAIN) ||
- (errno == EWOULDBLOCK) ||
- (errno == ENOBUFS) ||
- (errno == ENOMEM))
- locallimit++;
- fprintf(stderr, "preload send: %s\n", strerror(-ret));
- break;
- }
- }
-
- /* required only before the interrupted flag check */
- (void) signal(SIGINT, interrupt);
-
- /* main loop */
- for (;;) {
- struct timespec now, ts;
- fd_set rfds;
-
- /* immediate loop exit conditions */
- if (interrupted) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("interrupted\n");
- break;
- }
- if (fatal) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("got a fatal error\n");
- break;
- }
-
- /* get the date and use it */
- if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
- perror("clock_gettime(now)");
- fatal = 1;
- continue;
- }
- if ((period != 0) &&
- ((boot.tv_sec + period < now.tv_sec) ||
- ((boot.tv_sec + period == now.tv_sec) &&
- (boot.tv_nsec < now.tv_nsec)))) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached test-period\n");
- break;
- }
- if ((report != 0) &&
- ((dreport.tv_sec < now.tv_sec) ||
- ((dreport.tv_sec == now.tv_sec) &&
- (dreport.tv_nsec < now.tv_nsec))))
- reporting();
-
- /* compute the delay for the next send */
- due = last;
- if (rate == 1)
- due.tv_sec += 1;
- else if (rate != 0)
- due.tv_nsec += 1010000000 / rate;
- else
- due.tv_nsec += 1;
- while (due.tv_nsec >= 1000000000) {
- due.tv_sec += 1;
- due.tv_nsec -= 1000000000;
- }
- ts = due;
- ts.tv_sec -= now.tv_sec;
- ts.tv_nsec -= now.tv_nsec;
- while (ts.tv_nsec < 0) {
- ts.tv_sec -= 1;
- ts.tv_nsec += 1000000000;
- }
- /* the send was already due? */
- if (ts.tv_sec < 0) {
- ts.tv_sec = ts.tv_nsec = 0;
- latesent++;
- }
-
- /* pselect() */
- FD_ZERO(&rfds);
- FD_SET(sock, &rfds);
- ret = pselect(sock + 1, &rfds, NULL, NULL, &ts, NULL);
- if (ret < 0) {
- if (errno == EINTR)
- continue;
- perror("pselect");
- fatal = 1;
- continue;
- }
-
- /* packet(s) to receive */
- while (ret == 1) {
- if (ipversion == 4)
- receive4();
- else
- receive6();
- if (recv(sock, ibuf, sizeof(ibuf), MSG_PEEK) <= 0)
- ret = 0;
- else
- multrcvd++;
- }
- if (fatal)
- continue;
-
- /* check receive loop exit conditions */
- if ((numreq[0] != 0) && ((int) xscount0 >= numreq[0])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached num-request0\n");
- break;
- }
- if ((numreq[1] != 0) && ((int) xscount2 >= numreq[1])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached num-request2\n");
- break;
- }
- if ((maxdrop[0] != 0) &&
- ((int) (xscount0 - xrcount0) > maxdrop[0])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached max-drop%s (absolute)\n",
- simple != 0 ? "" : "0");
- break;
- }
- if ((maxdrop[1] != 0) &&
- ((int) (xscount2 - xrcount2) > maxdrop[1])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached max-drop2 (absolute)\n");
- break;
- }
- if ((maxpdrop[0] != 0.) &&
- (xscount0 > 10) &&
- (((100. * (xscount0 - xrcount0)) / xscount0)
- > maxpdrop[0])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached max-drop%s (percent)\n",
- simple != 0 ? "" : "0");
- break;
- }
- if ((maxpdrop[1] != 0.) &&
- (xscount2 > 10) &&
- (((100. * (xscount2 - xrcount2)) / xscount2)
- > maxpdrop[1])) {
- if ((diags != NULL) && (strchr(diags, 'e') != NULL))
- printf("reached max-drop2 (percent)\n");
- break;
- }
-
- /* compute how many packets to send */
- if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
- perror("clock_gettime(now2)");
- fatal = 1;
- continue;
- }
- if ((now.tv_sec > due.tv_sec) ||
- ((now.tv_sec == due.tv_sec) &&
- (now.tv_nsec >= due.tv_nsec))) {
- double tosend;
-
- if (rate != 0) {
- tosend = (now.tv_nsec - due.tv_nsec) / 1e9;
- tosend += now.tv_sec - due.tv_sec;
- tosend *= rate;
- tosend += 1;
- if (tosend > (double) aggressivity)
- i = aggressivity;
- else
- i = (int) tosend;
- } else
- i = aggressivity;
- compsend += i;
- /* send packets */
- for (;;) {
- if (ipversion == 4)
- ret = send4();
- else
- ret = send6();
- if (ret < 0) {
- if ((errno == EAGAIN) ||
- (errno == EWOULDBLOCK) ||
- (errno == ENOBUFS) ||
- (errno == ENOMEM))
- locallimit++;
- fprintf(stderr,
- "send: %s\n", strerror(-ret));
- break;
- }
- i--;
- if (i == 0)
- break;
- /* check for late packets to receive */
- if (recv(sock, ibuf, sizeof(ibuf),
- MSG_PEEK) > 0) {
- latercvd++;
- if (ipversion == 4)
- receive4();
- else
- receive6();
- }
- }
- } else
- /* there was no packet to send */
- shortwait++;
- }
-
- /* after main loop: finished */
- if (clock_gettime(CLOCK_REALTIME, &finished) < 0)
- perror("clock_gettime(finished)");
-
- /* wrapped stop */
- if (wrapped != NULL) {
- pid_t pid;
-
- pid = fork();
- if (pid == 0)
- (void) execlp(wrapped, "stop", (char *) NULL);
- }
-
- /* main statictics */
- if (xscount2 == 0)
- printf("sent: %llu, received: %llu (drops: %lld)\n",
- (unsigned long long) xscount0,
- (unsigned long long) xrcount0,
- (long long) (xscount0 - xrcount0));
- else
- printf("sent: %llu/%llu, received: %llu/%llu "
- "(drops: %lld/%lld)\n",
- (unsigned long long) xscount0,
- (unsigned long long) xscount2,
- (unsigned long long) xrcount0,
- (unsigned long long) xrcount2,
- (long long) (xscount0 - xrcount0),
- (long long) (xscount2 - xrcount2));
- printf("tooshort: %llu, orphans: %llu, local limits: %llu\n",
- (unsigned long long) tooshort,
- (unsigned long long) orphans,
- (unsigned long long) locallimit);
-
- /* print the rate */
- if (finished.tv_sec != 0) {
- double dall, erate;
-
- dall = (finished.tv_nsec - boot.tv_nsec) / 1e9;
- dall += finished.tv_sec - boot.tv_sec;
- erate = xrcount0 / dall;
- if (rate != 0)
- printf("rate: %f (expected %d)\n", erate, rate);
- else
- printf("rate: %f\n", erate);
- }
-
- /* rate processing instrumentation */
- if ((diags != NULL) && (strchr(diags, 'i') != NULL)) {
- printf("latesent: %llu, compsend: %llu, shortwait: %llu\n"
- "multrcvd: %llu, latercvd: %llu, collected:%llu/%llu\n",
- (unsigned long long) latesent,
- (unsigned long long) compsend,
- (unsigned long long) shortwait,
- (unsigned long long) multrcvd,
- (unsigned long long) latercvd,
- (unsigned long long) collected[0],
- (unsigned long long) collected[1]);
- }
-
- /* round-time trip statistics */
- if (xrcount2 != 0) {
- double avg0, avg2, stddev0, stddev2;
-
- avg0 = dsum0 / xrcount0;
- avg2 = dsum2 / xrcount2;
- stddev0 = sqrt(dsumsq0 / xrcount0 - avg0 * avg0);
- stddev2 = sqrt(dsumsq2 / xrcount2 - avg2 * avg2);
- printf("RTT0: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
- dmin0 * 1e3, avg0 * 1e3, dmax0 * 1e3, stddev0 * 1e3);
- printf("RTT2: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
- dmin2 * 1e3, avg2 * 1e3, dmax2 * 1e3, stddev2 * 1e3);
- } else if (xrcount0 != 0) {
- double avg, stddev;
-
- avg = dsum0 / xrcount0;
- stddev = sqrt(dsumsq0 / xrcount0 - avg * avg);
- printf("RTT%s: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
- simple != 0 ? "" : "0",
- dmin0 * 1e3, avg * 1e3, dmax0 * 1e3, stddev * 1e3);
- }
-
- /* (first) server-ID option content */
- if ((diags != NULL) && (strchr(diags, 's') != NULL) &&
- !ISC_TAILQ_EMPTY(&xrcvd0)) {
- struct exchange *x;
- size_t n;
-
- printf("server-id: ");
- x = ISC_TAILQ_FIRST(&xrcvd0);
- if (ipversion == 4)
- n = 2;
- else
- n = 4;
- for (; n < x->sidlen; n++)
- printf("%02hhx", x->sid[n]);
- printf("\n");
- }
-
- /* all time-stamps */
- if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
- !ISC_TAILQ_EMPTY(&xrcvd0)) {
- struct exchange *x;
-
- printf("\n\n");
- ISC_TAILQ_FOREACH(x, &xrcvd0, gchain)
- printf("%ld.%09ld %ld.%09ld\n",
- (long) x->ts0.tv_sec, x->ts0.tv_nsec,
- (long) x->ts1.tv_sec, x->ts1.tv_nsec);
-
- }
- if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
- !ISC_TAILQ_EMPTY(&xrcvd2)) {
- struct exchange *x;
-
- printf("--\n");
- ISC_TAILQ_FOREACH(x, &xrcvd2, gchain)
- printf("%ld.%09ld %ld.%09ld %ld.%09ld %ld.%09ld\n",
- (long) x->ts0.tv_sec, x->ts0.tv_nsec,
- (long) x->ts1.tv_sec, x->ts1.tv_nsec,
- (long) x->ts2.tv_sec, x->ts2.tv_nsec,
- (long) x->ts3.tv_sec, x->ts3.tv_nsec);
-
- }
- if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
- !ISC_TAILQ_EMPTY(&xrcvd0))
- printf("\n\n");
-
- /* template(s) */
- if ((diags != NULL) && (strchr(diags, 'T') != NULL)) {
- size_t n;
-
- if (ipversion == 4) {
- printf("length = %zu\n", length_discover4);
- printf("xid offset = %d\n", DHCP_OFF_XID);
- printf("xid length = 4\n");
- printf("random offset = %zu\n", random_discover4);
- printf("content:\n");
- for (n = 0; n < length_discover4; n++) {
- printf("%s%02hhx",
- (n & 15) == 0 ? "" : " ",
- template_discover4[n]);
- if ((n & 15) == 15)
- printf("\n");
- }
- if ((n & 15) != 15)
- printf("\n");
- if (simple != 0)
- goto doneT;
- printf("--\n");
- printf("length = %zu\n", length_request4);
- printf("xid offset = %d\n", DHCP_OFF_XID);
- printf("xid length = 4\n");
- printf("random offset = %zu\n", random_request4);
- if (elapsed_request4 > 0)
- printf("secs offset = %zu\n",
- elapsed_request4);
- printf("server-id offset = %zu\n", serverid_request4);
- printf("server-id length = %d\n", DHCP_OPTLEN_SRVID);
- printf("content:\n");
- printf("requested-ip-address offset = %zu\n",
- reqaddr_request4);
- printf("requested-ip-address length = %d\n", 4);
- for (n = 0; n < length_request4; n++) {
- printf("%s%02hhx",
- (n & 15) == 0 ? "" : " ",
- template_request4[n]);
- if ((n & 15) == 15)
- printf("\n");
- }
- printf("\n");
- } else {
- printf("length = %zu\n", length_solicit6);
- printf("xid offset = %d\n", DHCP6_OFF_XID);
- printf("xid length = 3\n");
- printf("random offset = %zu\n", random_solicit6);
- for (n = 0; n < length_solicit6; n++) {
- printf("%s%02hhx",
- (n & 15) == 0 ? "" : " ",
- template_solicit6[n]);
- if ((n & 15) == 15)
- printf("\n");
- }
- if ((n & 15) != 15)
- printf("\n");
- if (simple != 0)
- goto doneT;
- printf("--\n");
- printf("length = %zu\n", length_request6);
- printf("xid offset = %d\n", DHCP_OFF_XID);
- printf("xid length = 4\n");
- printf("random offset = %zu\n", random_request6);
- if (elapsed_request6 > 0)
- printf("secs offset = %zu\n",
- elapsed_request6);
- printf("server-id offset = %zu\n", serverid_request6);
- printf("content:\n");
- printf("requested-ip-address offset = %zu\n",
- reqaddr_request6);
- for (n = 0; n < length_request6; n++) {
- printf("%s%02hhx",
- (n & 15) == 0 ? "" : " ",
- template_request6[n]);
- if ((n & 15) == 15)
- printf("\n");
- }
- printf("\n");
- }
- }
- doneT:
-
- /* compute the exit code (and exit) */
- if (fatal)
- exit(1);
- else if ((xscount0 == xrcount0) && (xscount2 == xrcount2))
- exit(0);
- else
- exit(3);
-}
-
-#endif /* HAVE_GETIFADDRS */
More information about the bind10-changes
mailing list