BIND 10 trac2546, updated. fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71 [2546] Merge branch 'master' into trac2546
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu Dec 13 11:40:45 UTC 2012
The branch, trac2546 has been updated
via fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71 (commit)
via 550a5e0da25f7b3fa8051d988d70c116490b0f74 (commit)
via 286787bed2ffcf6ea6ff2da240edc9f78ef0660d (commit)
via c405fba738036d7df5fe2b3792bb70a9b9eda9f6 (commit)
via c328aed83b7d9506291283b6afbeabb4a5455b07 (commit)
via 92758ecffc245b5f9a86dbc8a2201a9c1397f7d8 (commit)
via de29c07129d41c96ee0d5eebdd30a1ea7fb9ac8a (commit)
via 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246 (commit)
via a56f7ff8c099d2d97a177cba073ee413ba3e9bd9 (commit)
via 6fe86386be0e7598633fe35999112c1a6e3b0370 (commit)
via cbb1e342dfef163cef8dba429aa8841093aff7af (commit)
via a2b2bf47cbbe033774013a875e3197c37fd71cce (commit)
via 8792cbaca81edb2106863f8043fe490d148db54a (commit)
via 9c2193e32b9c07dfe1db532cb26de1c1e16d1b48 (commit)
via 6aa3a421c244e6caa57e42d9dd67fb2326ce51b5 (commit)
via 0fab5f49d919476e7c58024081f3b939be51ff15 (commit)
via a40fac0707fac08e85b06974092bbdc15720f37f (commit)
via 196c4d239ece786aa29b86e6b48bef337ca330fc (commit)
via 1f943baf9448dcec53f5bcde3e9d21fae2eeb121 (commit)
via 0462436914ec6bccff8e7825ba3f55d50acda03e (commit)
via e65b7b36f60f14b7abe083da411e6934cdfbae7a (commit)
via d81727d1263a242de4ce8155f7e7708a91b37058 (commit)
via 0a4faa07777189ed9c25211987a1a9b574015a95 (commit)
via 99b2935b21ba5ae3dd524ad5b781e70e4914f0e4 (commit)
via 3de3abb8876fd8037712c9b6b5f216a0d13e59ef (commit)
via 74a0abe5a6d10b28e4a3e360e87b129c232dea68 (commit)
via 27442da4487da8da452967438bb8c784d0ac05c5 (commit)
via 422a1fcd67e40f3e398c292b50952cc09bce2623 (commit)
via 044c4779138290c78c20018d7f083aad7604d823 (commit)
via a9eef903b04db257e42e05b1a296251d4ebc6c04 (commit)
via 7d7b75217f01f143f759b075005c5da834b172e6 (commit)
via 230ccceb4112042ce3aadc7cfd6c9780afa23bbb (commit)
via c0be62cb0f27a0855c007beabeb0c3ba3c4fb0ff (commit)
via 3f9c7bb8a987dc1b4e2a9266ac95cf231d8fe621 (commit)
via 0cf505f04be53e6f198cb0d091bd90d74e597cbe (commit)
via 669a650ec38562f5459a5144cf8549df1b3f1cc4 (commit)
via b73262553443c404f3211913272a2ff89be904da (commit)
via 8627b6eb8915407f8bf29786b70916b4b6ac0567 (commit)
via 172e704b668189d6057c4bfc80403edd59aadd5c (commit)
via 8ad7de967cfee9788fa269640f1ce9e0d302ee04 (commit)
via 00554310f20b5a29fbad6a6a9ba9856c5570853a (commit)
via eb5b276de3dc4c556c818ebfab63a14bf2f562eb (commit)
via 0a9dafbece0545af535494a7e5745a14c87ed9f5 (commit)
via 8065343bc1c50353260809254e1994642f2b1763 (commit)
via 1572782282cd6b5835ad20f5d8538511616a7e2b (commit)
via 8afd799648279d29c8fd9f2505383bf6ebbf0730 (commit)
via 807cc540e16eedd53d714a4d9b61a2c24ae31c30 (commit)
via 2a69f5cd6a1badf9fb1538cd8f1ca56cef054ba7 (commit)
via 7e30c020cd6c0babcf1d2c426ce72eb6a83a7fe4 (commit)
via 17fb43189b8e6a8defa8f6e0a01b13d8ded65d16 (commit)
via 8bf2c98d2e62d95c8f86d41895f4cfaca3371d68 (commit)
via 12dba9fb977f4ca3e62da1f0e29739ca5a534ea8 (commit)
via c4b7c85d1a314e59b20aa055d81cff553b3b8de3 (commit)
via 3e2d8b00ba86773f3cba03f935e5847cee109f84 (commit)
via 32011f9a367b479a983dbb32e877590b18fbc275 (commit)
via 02f306c854846cf972886c8f42e1b6e642c89e4a (commit)
via 6af3de2d2ee4b43bb5056d8b11cba164ac693346 (commit)
via 366f210b8217811fb1cd96ed996f3491c7d9766f (commit)
via 52a60eb5c950491c450ec933f66a56318b4da669 (commit)
via 331a48e08d6c155f68eda2218fc04ec7cc276cbb (commit)
via 512ccc6d8b7bc49b085289226407fe741ebe7a54 (commit)
via 1378932f029f1e74cc606013778863161feca8e4 (commit)
via dfebd6128b4088ba489080cea7c53f141dce90ab (commit)
via d9d6998c28cfcf825fb2845acc3b4eb705e991a4 (commit)
via 9430b1fef5b2cc99e97b4cfaf6ccd518bcd76198 (commit)
via 0d69a3ddefcc66ffcf05683763ef2c7f27b7c392 (commit)
via c59714604459a820819a288ff03c4dadebda098b (commit)
via 7e8e8bcaaba5f836b3868f6601e8b4e8314a431e (commit)
via e6ebe1de8e6ab2982869ba27f1719e63fddeb0e3 (commit)
via de66fdd73b8f545768166b7406be878c137cc019 (commit)
via cdbe6dedf437b052cb60777c470cfda312f50b43 (commit)
via 029ac581f439485d797d195914f52a60eb593a60 (commit)
via e8a23d31110a2824368bff0c41f79016d696f613 (commit)
via f7e301237c7806b582172fcf80a892659a3d9e32 (commit)
via 4f806512c8b2a99f3b1dc43fda7130ec81d21c1f (commit)
via 87cd15b3cc34738753d97e62a07681b245350f25 (commit)
via 3c17e9586c94a0e4331cafe79e379e71f3d8a561 (commit)
via bae03c0fbf0a82c2b88cb683fc81c30ab7f70ad0 (commit)
via 231bfc60a9327295a2431f7d04ed5a26962d6236 (commit)
via f1f6c26aa9f45367159229de64ca1545c87d51ac (commit)
via dd2d08d8cb0ed409fd8a423c988db7a0979e08eb (commit)
via 0589469f4a57ee95f16ea7e316365ae0641c4c3c (commit)
via c84efb2586d129061f0fc0e1bcf291e0582b0f9d (commit)
via bdeb3c67d12483d7ed6945e7d190195764e24fcc (commit)
via 65a83c79b14885d4a4106e1a707ef431b72053e4 (commit)
via 2fd85850524510bb6d4452f2c9b863400b3949dc (commit)
via 233aebcb70f15914e141fef4982ad7d0028fbd9f (commit)
via ec4581da1a15031128504cf97c8c578669422126 (commit)
via bedf7f9f268f67f5522e620a385452cf6083b4ff (commit)
via 70db0c50bf842765d90e2a6b3abf4f5360f31281 (commit)
via 609e5bf4005731160716d1d63d3eebf402a14f1d (commit)
via 71cf0a22ba08d32908a610b749e9aefe1c4dda1a (commit)
via 40d54f7e2b04dc53be81a24e50f8e66be968e6cb (commit)
via 30e529ac56321e0654bf8bbe48412b851bd06bb9 (commit)
via c831a1e66e88bceb6a317dd8de0796b315681f4f (commit)
via d506b61e5f2e50d228ac8541d6927594861a25e5 (commit)
via 38db6aff7d759e5f271cf0d369459b361c8e60a7 (commit)
via d6fb00a3198fe327cdbf4ebfe14f47eb23086325 (commit)
via f2a03c4538343e2a6941e5037925a63a7e71f68c (commit)
via 936137b0b10508b395cbd4a71ad07c1ec0ffb655 (commit)
via cacc927a7096327c8e19479058258cecec0eb27e (commit)
via b1d0e42774ef51b404be44ee53cb930a630a63bb (commit)
via fca9ec102de26345c4dafb193f85a0560322f295 (commit)
via ee0836a4c450db59764158e9f9c58acad7a1c4a4 (commit)
via 7c0a4abbb4cce4460c8b0cfd9f1447e0cc37644d (commit)
via 9b8747917cc300904783b403a64c2a11ebdb5ec0 (commit)
via daca34509f65af73d06ce071340668258539f6c6 (commit)
via 650e0b501ab48af61704efe9cb503490716c8eec (commit)
via 45c04beca6f7d07f1a2c779baf3a7a8ba26763a2 (commit)
via 0b06fc6c1993467408bdb7f109affe029a834e3d (commit)
via bc44ee8bf3c35cacaad05751d75e8789aa5c9108 (commit)
via b561ee0919676f6e9c34329151c2b46d60f1824b (commit)
via 372f3a19f5bad9ed51e50d083063fccbe1ff6900 (commit)
via f634e891b21b4533c16871bab7bcd65f6422986d (commit)
via a0662ae5601ee75f3e99b2b53d4f3376b5e6a037 (commit)
via 10e87104e0b7977804c8c55275a403602dcbb2e5 (commit)
via 6b59d1bc4fe7976ea6c1cf7820649824b4135e07 (commit)
via 5c50504446f431198d329d31b62e076d65e3eacb (commit)
via 594916b524ff40cd66ce7891ce743c70a9d0aa68 (commit)
via 59903362374b1fca9612fe5a872579dd4a4d9873 (commit)
via 7cf0d504794bbf42d15015af1d42ba1cf7fcd82f (commit)
via ad7b96467ceeeade2ca9bc16ba8f02ecadc1ed82 (commit)
via a5330334a54791088394fe6b726666b9ae6671b9 (commit)
via 9c4e97e7f8cfe7f8001c1491f9f7c2e632c736b2 (commit)
via 1dc0f0871d7045e8fb57bb0767fc31a2f7e3fe80 (commit)
via 1bbe13152cea9e759bb0423750f244be58eff578 (commit)
via 8a21830a250996c93b1b053c56a4dc9bb7bb4750 (commit)
via ac111116c588c5b611e1363d4fe11c7ee8f4e495 (commit)
via bbefd6bc4137c3345845d09e86be68b81770718f (commit)
via 84e441c75606aff87131e7d84010679a11f242ce (commit)
via 159af7d5de17a74269cd4af69ac3b569495c4a4e (commit)
via 54663a3c7bda67a6813d2478ff44c8a07d9c48b8 (commit)
via 00eee13bde95c741890ad3ca52967c11076c5087 (commit)
via 22a33dfddc0b29a506b57c3f76bace8d15ec6df6 (commit)
via 7793f98f619c3422ca3fa26795a5861b0fbab4fa (commit)
via c806c0c3184caf046e124f36028122a965078408 (commit)
via f1035ba18d68d4a1fa89719a9dc8e5eb6ae24d84 (commit)
via 0740a5d8df7f65518b2b87662b4375630e1838ef (commit)
via 045d32e4a9f14e7deec58e5e7d55867a34325764 (commit)
via eba06eeedde4091fed26761f696a2aab5535074a (commit)
via 93d27cba1a47182b5f94e45b5d569386f2a1ad25 (commit)
via 8a966b8d7359a852478e380e89e94cfe2f12d5bd (commit)
via 2c2459fd09fd1fb9b2c741eca0b93a8550f3f9b3 (commit)
via 145a4ed5a89c92d93303604ceff83ec3d17ff87d (commit)
via 41ccdad87bfc96af06dd83ecd143e4277795eb5f (commit)
via 8682cdcb920f12df639aaa0e7720afb9b03bf849 (commit)
via 0688fdb85ded658b2ecf5aceb6e4691cb921ddfb (commit)
via cfdfb58ce442816cce069453d4051cc008691b74 (commit)
via 08a5a3d31220c660530bd69cccc8c27e1a789312 (commit)
via 7f6ecb5e40c6acede82fb08a7d4c477030a48b99 (commit)
via f6c776bc4a3cea0eae628d1580ab10843be06dfe (commit)
via bc4926dac23a819a909992abf58df64d62647c68 (commit)
via fa3e24bb027671b5c53a5fa1b004920cf8252f0f (commit)
via a11abf30b29eda75f8680c8287b57db1abe612e8 (commit)
via 996d6ff3b0e79c723332d03296cbbe88b552d373 (commit)
via 924c7a481efc5aa34c8bc73efb395c2c80282cc0 (commit)
via 8cb9e9efb9cd47301e6784840142931631e1664f (commit)
via f117a66b8be41e88a18a27bd7b2f1275b624767a (commit)
via 77c012c47a2bd2ff161b8b62df065b3daa87ff73 (commit)
via d51421238cd7aa392c6baa967ee977a6d3dc8b7a (commit)
via a9ebc379b9d2f2501c8a4d497bb715f6c100cea8 (commit)
via 1ab0c5acf4fa6b8a1a99068685bb2a27d6dd3dc8 (commit)
via 15249e567c9cfae135c1a2bc1a110b4a635b11ab (commit)
via da089cd9b9dc3aa3cb13f1c02e362b870e524b20 (commit)
via 5450b258dbeaaf876fd35c52a1191e4f083a620e (commit)
via b1c8e1ce93d760fa32e7b977910faa9ed5fc4c30 (commit)
via 58f0aac3a8f36f335c0546f44122ff43f42f5761 (commit)
via 84fbdaeddb176af0a5cd73caf76698208b4e94a7 (commit)
via 09f21f49fa1e214af117262c305efb3968becd8f (commit)
via 36d2b848c6badf00bae61bd958b891b7e2acc8cd (commit)
via 4f52e898a7b69228cf4a8ab70d78844ad9dc45e1 (commit)
via c2c64eae1f3e6140bbe30e963797b9a4efa5a9f8 (commit)
via 230de14b1b3e31b2f436cfd370e149a98926522e (commit)
via 54a707d39ae1943731ed2043f8d44424c434f0e3 (commit)
via 51bd304db036e8d2e6008a98b2dae01eea126e41 (commit)
via da1b2d7dc0a367ed168b366df1ab5c5af82260d4 (commit)
via d5c0c97ef8bf7d4b9ae2709b8a92eb1532f4c9b9 (commit)
via a7b31b7b074db8359b5c93c3b0d37c807a78ad7e (commit)
via 7503458897ccad85875ef3bc929ec803750179e8 (commit)
via 37da25f2eca6b2af80cc5bc2a7e65c5644acf2fb (commit)
via 0f228f2d53a626d647aa728585342c889ff8f846 (commit)
via 8b31b93c4c62b76b3ec70d0e9c1a18c0f51bd459 (commit)
via f9445ffa2d128ab0730abb3592e75475023ba755 (commit)
via 494bbca16cea584ba6ad5d2c5f9a273d809f902d (commit)
via 67f4798a8a2457b8d284b75dcf1a6bcfeb8a0fa5 (commit)
via 32e88854b4ab9b2484d1f5ad0597869c8d1f5da4 (commit)
via 9d23c50d12b04b51c10889fe9b476823550bd733 (commit)
via 83d4f414a296dfc757a7a4f6e8c8fcf7bd24ca2e (commit)
via 424b8d83f6059738f2edb01564ec9ce1db223b08 (commit)
via 3590bad22f52396c91663cc75b8548f6df134c60 (commit)
via 4a9b794019090fd826688bca0aee4dc565756cc1 (commit)
via 180a437742219f6266934738962663c65ab748f6 (commit)
via 6210dae2a6749d8c1e328de433c91058fd54b756 (commit)
via 6b8c62acd872220aeb061f7c6e0fe50348ba3d60 (commit)
via b0db66ca291884dd9220cda078d9ad3e91d0eb67 (commit)
via 71739287e490437d2f1278e02ba9232afa925a48 (commit)
via 7ba306a2e14dd31e1bf8350c2d27b9741af7e101 (commit)
via 9b6c74f8e85b29db87fb4724698d9fb9cc53dcc4 (commit)
via 356a8b2e2cdbd2470eb134bfe08f80fb54028abb (commit)
via d0af0ecaadd8950008ef65ee2b40b56fa21a1e82 (commit)
via 142825f0caeba214a4d1884be4067c62d1cc3551 (commit)
via 4b59798e65bfc49e153a4fd96fcc684c1fbe91b6 (commit)
via d2c2edbfa7d78ebeae4c79eef532814ac7072556 (commit)
via 552657fff4e4538628e7d2b98c4fbe93a07d307c (commit)
via 650a6c1b4f315e6e89de673502d5a82359f424d8 (commit)
from 52d965040ccb6e919a34abd95240fee3718c0247 (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 fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71
Merge: 52d9650 550a5e0
Author: Stephen Morris <stephen at isc.org>
Date: Thu Dec 13 11:38:21 2012 +0000
[2546] Merge branch 'master' into trac2546
Conflicts:
doc/devel/mainpage.dox
src/bin/dhcp4/ctrl_dhcp4_srv.h
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp6/dhcp6.dox
Also modified the following files during the
resolution of the merge issues:
src/bin/dhcp4/config_parser.cc
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/dhcp4.dox
src/bin/dhcp6/config_parser.cc
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
src/lib/dhcp/option_custom.h
src/lib/dhcp/pkt4.h
src/lib/dhcpsrv/database_backends.dox
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 53 +-
configure.ac | 4 +-
doc/devel/mainpage.dox | 8 +-
doc/guide/bind10-guide.xml | 113 ++-
src/bin/auth/main.cc | 6 +-
src/bin/bind10/bind10_src.py.in | 81 +-
src/bin/bind10/tests/bind10_test.py.in | 765 ++++++++++++++++++-
src/bin/cfgmgr/b10-cfgmgr.py.in | 2 +-
src/bin/cmdctl/cmdctl.py.in | 2 +-
src/bin/ddns/ddns.py.in | 2 +-
src/bin/dhcp4/Makefile.am | 2 +
src/bin/dhcp4/config_parser.cc | 772 ++++++++++++++++++++
src/bin/{dhcp6 => dhcp4}/config_parser.h | 95 ++-
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 27 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 2 +
src/bin/dhcp4/dhcp4.dox | 68 ++
src/bin/dhcp4/dhcp4.spec | 80 +-
src/bin/dhcp4/dhcp4_messages.mes | 20 +
src/bin/dhcp4/main.cc | 8 +-
src/bin/dhcp4/tests/Makefile.am | 3 +
src/bin/dhcp4/tests/config_parser_unittest.cc | 294 ++++++++
src/bin/dhcp6/config_parser.cc | 39 +-
src/bin/dhcp6/config_parser.h | 25 +-
src/bin/dhcp6/dhcp6.dox | 2 +-
src/bin/dhcp6/dhcp6_messages.mes | 27 +
src/bin/dhcp6/dhcp6_srv.cc | 332 ++++++---
src/bin/dhcp6/dhcp6_srv.h | 80 +-
src/bin/dhcp6/main.cc | 10 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 352 +++++++--
src/bin/resolver/main.cc | 12 +-
src/bin/stats/stats.py.in | 4 +-
src/bin/stats/stats_httpd.py.in | 4 +-
src/bin/xfrin/b10-xfrin.xml | 10 +-
src/bin/xfrin/tests/xfrin_test.py | 50 +-
src/bin/xfrin/xfrin.py.in | 47 +-
src/bin/xfrin/xfrin_messages.mes | 9 +-
src/bin/xfrout/xfrout.py.in | 2 +-
src/bin/zonemgr/zonemgr.py.in | 2 +-
src/lib/datasrc/memory/zone_data.cc | 22 +-
src/lib/datasrc/memory/zone_data.h | 14 +-
src/lib/datasrc/memory/zone_data_updater.cc | 2 +-
.../datasrc/tests/memory/testdata/2503-test.zone | 13 +
src/lib/datasrc/tests/memory/testdata/Makefile.am | 1 +
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 34 +-
.../datasrc/tests/memory/zone_finder_unittest.cc | 25 +
src/lib/dhcp/Makefile.am | 2 +
src/lib/dhcp/libdhcp++.cc | 68 +-
src/lib/dhcp/libdhcp++.h | 13 +-
src/lib/dhcp/option.cc | 4 +
src/lib/dhcp/option.h | 15 +-
src/lib/dhcp/option6_ia.h | 9 +-
src/lib/dhcp/option_custom.cc | 334 ++++++++-
src/lib/dhcp/option_custom.h | 191 ++++-
src/lib/dhcp/option_data_types.cc | 39 +
src/lib/dhcp/option_data_types.h | 44 +-
src/lib/dhcp/option_definition.cc | 93 ++-
src/lib/dhcp/option_definition.h | 2 +-
src/lib/dhcp/pkt4.h | 2 +-
src/lib/dhcp/pkt6.cc | 72 +-
src/lib/dhcp/pkt6.h | 43 +-
src/lib/dhcp/std_option_defs.h | 184 +++++
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/libdhcp++_unittest.cc | 172 ++++-
src/lib/dhcp/tests/option6_ia_unittest.cc | 11 +-
src/lib/dhcp/tests/option_custom_unittest.cc | 553 +++++++++++++-
src/lib/dhcp/tests/option_data_types_unittest.cc | 491 +++++++++++++
src/lib/dhcp/tests/option_definition_unittest.cc | 3 +-
src/lib/dhcp/tests/option_unittest.cc | 23 +
src/lib/dhcp/tests/pkt6_unittest.cc | 73 ++
src/lib/dhcpsrv/cfgmgr.cc | 8 +
src/lib/dhcpsrv/cfgmgr.h | 19 +-
src/lib/dhcpsrv/database_backends.dox | 2 +-
src/lib/dns/master_lexer.h | 3 +-
src/lib/dns/python/tests/tsigkey_python_test.py | 6 +
src/lib/dns/python/tsigkey_python.cc | 17 +-
src/lib/dns/tests/tsigkey_unittest.cc | 9 +
src/lib/dns/tsigkey.cc | 12 +-
src/lib/dns/tsigkey.h | 22 +
src/lib/log/Makefile.am | 1 +
src/lib/log/README | 14 +-
src/lib/log/buffer_appender_impl.cc | 96 +++
src/lib/log/buffer_appender_impl.h | 118 +++
src/lib/log/logger_manager.cc | 6 +-
src/lib/log/logger_manager.h | 24 +-
src/lib/log/logger_manager_impl.cc | 80 +-
src/lib/log/logger_manager_impl.h | 50 +-
src/lib/log/logger_support.cc | 4 +-
src/lib/log/logger_support.h | 6 +-
src/lib/log/tests/Makefile.am | 13 +
src/lib/log/tests/buffer_appender_unittest.cc | 146 ++++
src/lib/log/tests/buffer_logger_test.cc | 71 ++
...er_lock_test.sh.in => buffer_logger_test.sh.in} | 40 +-
src/lib/python/isc/config/cfgmgr.py | 9 +-
src/lib/python/isc/log/log.cc | 29 +-
src/lib/python/isc/log/tests/log_test.py | 22 +
tests/lettuce/features/xfrin_bind10.feature | 2 +-
96 files changed, 6159 insertions(+), 637 deletions(-)
create mode 100644 src/bin/dhcp4/config_parser.cc
copy src/bin/{dhcp6 => dhcp4}/config_parser.h (62%)
create mode 100644 src/bin/dhcp4/dhcp4.dox
create mode 100644 src/bin/dhcp4/tests/config_parser_unittest.cc
create mode 100644 src/lib/datasrc/tests/memory/testdata/2503-test.zone
create mode 100644 src/lib/dhcp/std_option_defs.h
create mode 100644 src/lib/dhcp/tests/option_data_types_unittest.cc
create mode 100644 src/lib/log/buffer_appender_impl.cc
create mode 100644 src/lib/log/buffer_appender_impl.h
create mode 100644 src/lib/log/tests/buffer_appender_unittest.cc
create mode 100644 src/lib/log/tests/buffer_logger_test.cc
copy src/lib/log/tests/{logger_lock_test.sh.in => buffer_logger_test.sh.in} (51%)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 67fbc1b..9164587 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,55 @@
+525. [func] tomek
+ b10-dhcp4: DHCPv4 server is now able to parse configuration. It
+ is possible to specify IPv4 subnets with dynamic pools within
+ them. Although configuration is accepted, it is not used yet. This
+ will be implemented shortly.
+ (Trac #2270, git de29c07129d41c96ee0d5eebdd30a1ea7fb9ac8a)
+
+524. [func] tomek
+ b10-dhcp6 is now able to handle RENEW messages. Leases are
+ renewed and REPLY responses are sent back to clients.
+ (Trac #2325, git 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246)
+
+523. [bug] muks
+ Fixed a problem in inmem NSEC3 lookup (for, instance when using a
+ zone with no non-apex names) which caused exceptions when the zone
+ origin was not added as an explicit NSEC3 record.
+ (Trac #2503, git 6fe86386be0e7598633fe35999112c1a6e3b0370)
+
+522. [func]* jelte
+ Configuration of TSIG keys for b10-xfrin has changed; instead of
+ specifying the full TSIG key (<name>:<base64>:<algo>) it now expects
+ just the name, and uses the global TSIG Key Ring like all the other
+ components (configuration list /tsig_keys/keys).
+ Note: this is not automatically updated, so if you use TSIG in
+ xfrin, you need to update your configuration.
+ (Trac #1351, git e65b7b36f60f14b7abe083da411e6934cdfbae7a)
+
+521. [func] marcin
+ Implemented definitions for DHCPv6 standard options identified
+ by codes up to 48. These definitions are now used by the DHCPv6
+ server to create instances of options being sent to a client.
+ (Trac #2491, git 0a4faa07777189ed9c25211987a1a9b574015a95)
+
+520. [func] jelte
+ The system no longer prints initial log messages to stdout
+ regardless of what logging configuration is present, but it
+ temporarily stores any log messages until the configuration is
+ processed. If there is no specific configuration, or if the
+ configuration cannot be accessed, it will still fall back to stdout.
+ Note that there are still a few instances where output is printed,
+ these shall be addressed separately.
+ Note also that, currently, in case it falls back to stdout (such as
+ when it cannot connect to b10-cfgmgr), all log messages are always
+ printed (including debug messages), regardless of whether -v was
+ used. This shall also be addressed in a future change.
+ (Trac #2445, git 74a0abe5a6d10b28e4a3e360e87b129c232dea68)
+
519. [bug] muks
- Fixed a problem in inmem NSEC lookup which caused assert failures
- in some cases.
+ Fixed a problem in inmem NSEC lookup which caused returning an
+ incorrect NSEC record or (in rare cases) assert failures
+ when a non-existent domain was queried, which was a sub-domain of
+ a domain that existed.
(Trac #2504, git 835553eb309d100b062051f7ef18422d2e8e3ae4)
518. [func] stephen
diff --git a/configure.ac b/configure.ac
index feeb9a9..3f7a269 100644
--- a/configure.ac
+++ b/configure.ac
@@ -903,11 +903,12 @@ AC_SUBST(MULTITHREADING_FLAG)
#
GTEST_LDFLAGS=
GTEST_LDADD=
-# TODO: set DISTCHECK_GTEST_CONFIGURE_FLAG for --with-gtest too
DISTCHECK_GTEST_CONFIGURE_FLAG=
if test "x$enable_gtest" = "xyes" ; then
+ DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=$gtest_path"
+
if test -n "$with_gtest_source" ; then
if test "x$GTEST_SOURCE" = "xyes" ; then
@@ -1375,6 +1376,7 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/console_test.sh
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
+ src/lib/log/tests/buffer_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
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index c2c69ca..295fd03 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -20,9 +20,11 @@
* - @subpage DataScrubbing
*
* @section DHCP
- * - @subpage dhcpv4
+ * - @subpage dhcp4
* - @subpage dhcpv4Session
- * - @subpage dhcpv6
+ * - @subpage dhcpv4ConfigParser
+ * - @subpage dhcpv4ConfigInherit
+ * - @subpage dhcp6
* - @subpage dhcpv6Session
* - @subpage dhcpv6ConfigParser
* - @subpage dhcpv6ConfigInherit
@@ -33,7 +35,7 @@
* - @subpage leasemgr
* - @subpage cfgmgr
* - @subpage allocengine
- * - @subpage dhcp-database-backends
+ * - @subpage dhcpDatabaseBackends
* - @subpage perfdhcpInternals
*
* @section misc Miscellaneous topics
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index da1ac13..61a9ee4 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -758,7 +758,7 @@ as a dependency earlier -->
If the configure fails, it may be due to missing or old
dependencies.
</para>
-
+
<note>
<para>For notes on configuring and building DHCPv6 with MySQL see <xref linkend="dhcp6-install">.</xref></para>
</note>
@@ -1841,10 +1841,8 @@ config set /Boss/components/b10-zonemgr/kind dispensable
<para>
The key ring lives in the configuration in "tsig_keys/keys". Most of
the system uses the keys from there — ACLs, authoritative server to
- sign responses to signed queries, and <command>b10-xfrout</command>
- to sign transfers. The <command>b10-xfrin</command> uses its own
- configuration for keys, but that will be fixed in Trac ticket
- <ulink url="http://bind10.isc.org/ticket/1351">#1351</ulink>.
+ sign responses to signed queries, and <command>b10-xfrin</command>
+ and <command>b10-xfrout</command> to sign transfers.
</para>
<para>
@@ -2722,6 +2720,15 @@ TODO
</section>
<section>
+ <title>TSIG</title>
+ If you want to use TSIG for incoming transfers, a system wide TSIG
+ key ring must be configured (see <xref linkend="tsig-key-ring"/>).
+ To specify a key to use, set tsig_key value to the name of the key
+ to use from the key ring.
+> <userinput>config set Xfrin/zones[0]/tsig_key "<option>example.key</option>"</userinput>
+ </section>
+
+ <section>
<title>Enabling IXFR</title>
<para>
As noted above, <command>b10-xfrin</command> uses AXFR for
@@ -3397,16 +3404,94 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
<section id="dhcp4-config">
<title>DHCPv4 Server Configuration</title>
<para>
- The DHCPv4 server does not have a lease database implemented yet
- nor any support for configuration, so the same set
- of configuration options (including the same fixed address)
- will be assigned every time.
+ Once the server is started, it can be configured. To view the
+ current configuration, use the following command in <command>bindctl</command>:
+ <screen>
+> <userinput>config show Dhcp4</userinput></screen>
+ When starting Dhcp4 daemon for the first time, the default configuration
+ will be available. It will look similar to this:
+ <screen>
+> <userinput>config show Dhcp4</userinput>
+Dhcp4/interface/ list (default)
+Dhcp4/renew-timer 1000 integer (default)
+Dhcp4/rebind-timer 2000 integer (default)
+Dhcp4/preferred-lifetime 3000 integer (default)
+Dhcp4/valid-lifetime 4000 integer (default)
+Dhcp4/subnet4 [] list (default)</screen>
</para>
+
+ <para>
+ To change one of the parameters, simply follow
+ the usual <command>bindctl</command> procedure. For example, to make the
+ leases longer, change their valid-lifetime parameter:
+ <screen>
+> <userinput>config set Dhcp4/valid-lifetime 7200</userinput>
+> <userinput>config commit</userinput></screen>
+ Please note that most Dhcp4 parameters are of global scope
+ and apply to all defined subnets, unless they are overridden on a
+ per-subnet basis.
+ </para>
+
+ <para>
+ The essential role of DHCPv4 server is address assignment. The server
+ has to be configured with at least one subnet and one pool of dynamic
+ addresses to be managed. For example, assume that the server
+ is connected to a network segment that uses the 192.0.2.0/24
+ prefix. The Administrator of that network has decided that addresses from range
+ 192.0.2.10 to 192.0.2.20 are going to be managed by the Dhcp4
+ server. Such a configuration can be achieved in the following way:
+ <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ Note that subnet is defined as a simple string, but the pool parameter
+ is actually a list of pools: for this reason, the pool definition is
+ enclosed in square brackets, even though only one range of addresses
+ is specified.</para>
+ <para>It is possible to define more than one pool in a
+ subnet: continuing the previous example, further assume that
+ 192.0.2.64/26 should be also be managed by the server. It could be written as
+ 192.0.2.64 to 192.0.2.127. Alternatively, it can be expressed more simply as
+ 192.0.2.64/26. Both formats are supported by Dhcp4 and can be mixed in the pool list.
+ For example, one could define the following pools:
+ <screen>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10-192.0.2.20", "192.0.2.64/26" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ The number of pools is not limited, but for performance reasons it is recommended to
+ use as few as possible. Space and tabulations in pool definitions are ignored, so
+ spaces before and after hyphen are optional. They can be used to improve readability.
+ </para>
+ <para>
+ The server may be configured to serve more than one subnet. To add a second subnet,
+ use a command similar to the following:
+ <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/subnet "192.0.3.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/pool [ "192.0.3.0/24" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ Arrays are counted from 0. subnet[0] refers to the subnet defined in the
+ previous example. The <command>config add Dhcp4/subnet4</command> adds
+ another (second) subnet. It can be referred to as
+ <command>Dhcp4/subnet4[1]</command>. In this example, we allow server to
+ dynamically assign all addresses available in the whole subnet.
+ </para>
+ <para>
+ When configuring a DHCPv4 server using prefix/length notation, please pay
+ attention to the boundary values. When specifying that the server should use
+ a given pool, it will be able to allocate also first (typically network
+ address) and the last (typically broadcast address) address from that pool.
+ In the aforementioned example of pool 192.0.3.0/24, both 192.0.3.0 and
+ 192.0.3.255 addresses may be assigned as well. This may be invalid in some
+ network configurations. If you want to avoid this, please use min-max notation.
+ </para>
+
<para>
- At this stage of development, the only way to alter the server
- configuration is to modify its source code. To do so, please
- edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
- parameters and recompile:
+ Note: Although configuration is now accepted, it is not internally used
+ by they server yet. At this stage of development, the only way to alter
+ server configuration is to modify its source code. To do so, please edit
+ src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
+ recompile:
<screen>
const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
const std::string HARDCODED_NETMASK = "255.255.255.0";
@@ -3416,7 +3501,7 @@ const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
- Lease database and configuration support is planned for 2012.
+ Lease database and configuration support is planned for end of 2012.
</para>
</section>
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index 1fe0f48..e90d199 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -147,7 +147,7 @@ main(int argc, char* argv[]) {
// Initialize logging. If verbose, we'll use maximum verbosity.
isc::log::initLogger(AUTH_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, true);
int ret = 0;
@@ -256,7 +256,9 @@ main(int argc, char* argv[]) {
// If we haven't registered callback for data sources, this will be just
// no-op.
- config_session->removeRemoteConfig("data_sources");
+ if (config_session != NULL) {
+ config_session->removeRemoteConfig("data_sources");
+ }
delete xfrin_session;
delete config_session;
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 36ad760..882653b 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -48,7 +48,7 @@ else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-
+
import subprocess
import signal
import re
@@ -76,7 +76,7 @@ import isc.bind10.socket_cache
import libutil_io_python
import tempfile
-isc.log.init("b10-boss")
+isc.log.init("b10-boss", buffer=True)
logger = isc.log.Logger("boss")
# Pending system-wide debug level definitions, the ones we
@@ -166,14 +166,14 @@ class ProcessStartError(Exception): pass
class BoB:
"""Boss of BIND class."""
-
+
def __init__(self, msgq_socket_file=None, data_path=None,
config_filename=None, clear_config=False,
verbose=False, nokill=False, setuid=None, setgid=None,
username=None, cmdctl_port=None, wait_time=10):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
-
+
The msgq_socket_file specifies the UNIX domain socket file that the
msgq process listens on. If verbose is True, then the boss reports
what it is doing.
@@ -216,6 +216,12 @@ class BoB:
self.clear_config = clear_config
self.cmdctl_port = cmdctl_port
self.wait_time = wait_time
+ self.msgq_timeout = 5
+
+ # _run_under_unittests is only meant to be used when testing. It
+ # bypasses execution of some code to help with testing.
+ self._run_under_unittests = False
+
self._component_configurator = isc.bind10.component.Configurator(self,
isc.bind10.special_component.get_specials())
# The priorities here make them start in the correct order. First
@@ -332,6 +338,7 @@ class BoB:
"""
logger.info(BIND10_KILLING_ALL_PROCESSES)
self.__kill_children(True)
+ self.components = {}
def _read_bind10_config(self):
"""
@@ -400,7 +407,7 @@ class BoB:
logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
except:
logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
-
+
return False
# The next few methods start the individual processes of BIND-10. They
@@ -408,21 +415,34 @@ class BoB:
# raised which is caught by the caller of start_all_processes(); this kills
# processes started up to that point before terminating the program.
+ def _make_process_info(self, name, args, env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ """
+ Wrapper around ProcessInfo(), useful to override
+ ProcessInfo() creation during testing.
+ """
+ return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr)
+
def start_msgq(self):
"""
Start the message queue and connect to the command channel.
"""
self.log_starting("b10-msgq")
- msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
- True, not self.verbose)
+ msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
+ self.c_channel_env,
+ True, not self.verbose)
msgq_proc.spawn()
self.log_started(msgq_proc.pid)
# Now connect to the c-channel
cc_connect_start = time.time()
while self.cc_session is None:
+ # if we are run under unittests, break
+ if self._run_under_unittests:
+ break
+
# if we have been trying for "a while" give up
- if (time.time() - cc_connect_start) > 5:
+ if (time.time() - cc_connect_start) > self.msgq_timeout:
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
@@ -434,7 +454,8 @@ class BoB:
# Subscribe to the message queue. The only messages we expect to receive
# on this channel are once relating to process startup.
- self.cc_session.group_subscribe("Boss")
+ if self.cc_session is not None:
+ self.cc_session.group_subscribe("Boss")
return msgq_proc
@@ -450,13 +471,14 @@ class BoB:
args.append("--config-filename=" + self.config_filename)
if self.clear_config:
args.append("--clear-config")
- bind_cfgd = ProcessInfo("b10-cfgmgr", args,
- self.c_channel_env)
+ bind_cfgd = self._make_process_info("b10-cfgmgr", args,
+ self.c_channel_env)
bind_cfgd.spawn()
self.log_started(bind_cfgd.pid)
- # Wait for the configuration manager to start up as subsequent initialization
- # cannot proceed without it. The time to wait can be set on the command line.
+ # Wait for the configuration manager to start up as
+ # subsequent initialization cannot proceed without it. The
+ # time to wait can be set on the command line.
time_remaining = self.wait_time
msg, env = self.cc_session.group_recvmsg()
while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
@@ -464,7 +486,7 @@ class BoB:
time.sleep(1)
time_remaining = time_remaining - 1
msg, env = self.cc_session.group_recvmsg()
-
+
if not self.process_running(msg, "ConfigManager"):
raise ProcessStartError("Configuration manager process has not started")
@@ -481,7 +503,7 @@ class BoB:
process, the log_starting/log_started methods are not used.
"""
logger.info(BIND10_STARTING_CC)
- self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+ self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler,
self.command_handler,
socket_file = self.msgq_socket_file)
@@ -499,7 +521,7 @@ class BoB:
The port and address arguments are for log messages only.
"""
self.log_starting(name, port, address)
- newproc = ProcessInfo(name, args, c_channel_env)
+ newproc = self._make_process_info(name, args, c_channel_env)
newproc.spawn()
self.log_started(newproc.pid)
return newproc
@@ -611,7 +633,6 @@ class BoB:
if self.msgq_socket_file is not None:
c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
- # try to connect, and if we can't wait a short while
try:
self.cc_session = isc.cc.Session(self.msgq_socket_file)
logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
@@ -679,7 +700,7 @@ class BoB:
except:
pass
# XXX: some delay probably useful... how much is uncertain
- # I have changed the delay from 0.5 to 1, but sometime it's
+ # I have changed the delay from 0.5 to 1, but sometime it's
# still not enough.
time.sleep(1)
self.reap_children()
@@ -728,17 +749,19 @@ class BoB:
return os.waitpid(-1, os.WNOHANG)
def reap_children(self):
- """Check to see if any of our child processes have exited,
- and note this for later handling.
+ """Check to see if any of our child processes have exited,
+ and note this for later handling.
"""
while True:
try:
(pid, exit_status) = self._get_process_exit_status()
except OSError as o:
- if o.errno == errno.ECHILD: break
+ if o.errno == errno.ECHILD:
+ break
# XXX: should be impossible to get any other error here
raise
- if pid == 0: break
+ if pid == 0:
+ break
if pid in self.components:
# One of the components we know about. Get information on it.
component = self.components.pop(pid)
@@ -760,11 +783,11 @@ class BoB:
"""
Restart any dead processes:
- * Returns the time when the next process is ready to be restarted.
+ * Returns the time when the next process is ready to be restarted.
* If the server is shutting down, returns 0.
* If there are no processes, returns None.
- The values returned can be safely passed into select() as the
+ The values returned can be safely passed into select() as the
timeout value.
"""
@@ -909,8 +932,10 @@ class BoB:
"""
if self._srv_socket is not None:
self._srv_socket.close()
- os.remove(self._socket_path)
- os.rmdir(self._tmpdir)
+ if os.path.exists(self._socket_path):
+ os.remove(self._socket_path)
+ if os.path.isdir(self._tmpdir):
+ os.rmdir(self._tmpdir)
def _srv_accept(self):
"""
@@ -1006,7 +1031,7 @@ boss_of_bind = None
def reaper(signal_number, stack_frame):
"""A child process has died (SIGCHLD received)."""
- # don't do anything...
+ # don't do anything...
# the Python signal handler has been set up to write
# down a pipe, waking up our select() bit
pass
@@ -1173,7 +1198,7 @@ and the created lock file must be writable for that user.
except KeyError:
pass
- # Next try getting information about the user, assuming user name
+ # Next try getting information about the user, assuming user name
# passed.
# If the information is both a valid user name and user number, we
# prefer the name because we try it second. A minor point, hopefully.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index ece6370..409790c 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -25,6 +25,7 @@ import bind10_src
import unittest
import sys
import os
+import os.path
import copy
import signal
import socket
@@ -34,6 +35,7 @@ import isc
import isc.log
import isc.bind10.socket_cache
import errno
+import random
from isc.testutils.parse_args import TestOptParser, OptsError
from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -366,6 +368,53 @@ class TestBoB(unittest.TestCase):
self.assertEqual(creator, bob._socket_cache._creator)
self.assertRaises(ValueError, bob.set_creator, creator)
+ def test_socket_srv(self):
+ """Tests init_socket_srv() and remove_socket_srv() work as expected."""
+ bob = BoB()
+
+ self.assertIsNone(bob._srv_socket)
+ self.assertIsNone(bob._tmpdir)
+ self.assertIsNone(bob._socket_path)
+
+ bob.init_socket_srv()
+
+ self.assertIsNotNone(bob._srv_socket)
+ self.assertNotEqual(-1, bob._srv_socket.fileno())
+ self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'),
+ bob._srv_socket.getsockname())
+
+ self.assertIsNotNone(bob._tmpdir)
+ self.assertTrue(os.path.isdir(bob._tmpdir))
+ self.assertIsNotNone(bob._socket_path)
+ self.assertTrue(os.path.exists(bob._socket_path))
+
+ # Check that it's possible to connect to the socket file (this
+ # only works if the socket file exists and the server listens on
+ # it).
+ s = socket.socket(socket.AF_UNIX)
+ try:
+ s.connect(bob._socket_path)
+ can_connect = True
+ s.close()
+ except socket.error as e:
+ can_connect = False
+
+ self.assertTrue(can_connect)
+
+ bob.remove_socket_srv()
+
+ self.assertEqual(-1, bob._srv_socket.fileno())
+ self.assertFalse(os.path.exists(bob._socket_path))
+ self.assertFalse(os.path.isdir(bob._tmpdir))
+
+ # These should not fail either:
+
+ # second call
+ bob.remove_socket_srv()
+
+ bob._srv_socket = None
+ bob.remove_socket_srv()
+
def test_init_alternate_socket(self):
bob = BoB("alt_socket_file")
self.assertEqual(bob.verbose, False)
@@ -461,6 +510,22 @@ class TestBoB(unittest.TestCase):
self.assertEqual({'command': ['shutdown', {'pid': 42}]},
bob.cc_session.msg)
+# Mock class for testing BoB's usage of ProcessInfo
+class MockProcessInfo:
+ def __init__(self, name, args, env={}, dev_null_stdout=False,
+ dev_null_stderr=False):
+ self.name = name
+ self.args = args
+ self.env = env
+ self.dev_null_stdout = dev_null_stdout
+ self.dev_null_stderr = dev_null_stderr
+ self.process = None
+ self.pid = None
+
+ def spawn(self):
+ # set some pid (only used for testing that it is not None anymore)
+ self.pid = 42147
+
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
@@ -490,6 +555,7 @@ class MockBob(BoB):
self.c_channel_env = {}
self.components = { }
self.creator = False
+ self.get_process_exit_status_called = False
class MockSockCreator(isc.bind10.component.Component):
def __init__(self, process, boss, kind, address=None, params=None):
@@ -661,6 +727,52 @@ class MockBob(BoB):
del self.components[12]
self.cmdctl = False
+ def _get_process_exit_status(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (53, 0)
+
+ def _get_process_exit_status_unknown_pid(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (42, 0)
+
+ def _get_process_exit_status_raises_oserror_echild(self):
+ raise OSError(errno.ECHILD, 'Mock error')
+
+ def _get_process_exit_status_raises_oserror_other(self):
+ raise OSError(0, 'Mock error')
+
+ def _get_process_exit_status_raises_other(self):
+ raise Exception('Mock error')
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+class MockBobSimple(BoB):
+ def __init__(self):
+ BoB.__init__(self)
+ # Set which process has been started
+ self.started_process_name = None
+ self.started_process_args = None
+ self.started_process_env = None
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+ def start_process(self, name, args, c_channel_env, port=None,
+ address=None):
+ self.started_process_name = name
+ self.started_process_args = args
+ self.started_process_env = c_channel_env
+ return None
+
class TestStartStopProcessesBob(unittest.TestCase):
"""
Check that the start_all_components method starts the right combination
@@ -930,6 +1042,9 @@ class MockComponent:
self.pid = lambda: pid
self.address = lambda: address
self.restarted = False
+ self.forceful = False
+ self.running = True
+ self.has_failed = False
def get_restart_time(self):
return 0 # arbitrary dummy value
@@ -938,6 +1053,15 @@ class MockComponent:
self.restarted = True
return True
+ def is_running(self):
+ return self.running
+
+ def failed(self, status):
+ return self.has_failed
+
+ def kill(self, forceful):
+ self.forceful = forceful
+
class TestBossCmd(unittest.TestCase):
def test_ping(self):
"""
@@ -1107,6 +1231,20 @@ class TestBossComponents(unittest.TestCase):
'process': 'cat'
}
}
+ self._tmp_time = None
+ self._tmp_sleep = None
+ self._tmp_module_cc_session = None
+ self._tmp_cc_session = None
+
+ def tearDown(self):
+ if self._tmp_time is not None:
+ time.time = self._tmp_time
+ if self._tmp_sleep is not None:
+ time.sleep = self._tmp_sleep
+ if self._tmp_module_cc_session is not None:
+ isc.config.ModuleCCSession = self._tmp_module_cc_session
+ if self._tmp_cc_session is not None:
+ isc.cc.Session = self._tmp_cc_session
def __unary_hook(self, param):
"""
@@ -1324,14 +1462,618 @@ class TestBossComponents(unittest.TestCase):
bob._component_configurator._components['test'] = (None, component)
self.__setup_restart(bob, component)
self.assertTrue(component.restarted)
- self.assertFalse(component in bob.components_to_restart)
+ self.assertNotIn(component, bob.components_to_restart)
# Remove the component from the configuration. It won't be restarted
# even if scheduled, nor will remain in the to-be-restarted list.
del bob._component_configurator._components['test']
self.__setup_restart(bob, component)
self.assertFalse(component.restarted)
- self.assertFalse(component in bob.components_to_restart)
+ self.assertNotIn(component, bob.components_to_restart)
+
+ def test_get_processes(self):
+ '''Test that procsses are returned correctly, sorted by pid.'''
+ bob = MockBob()
+
+ pids = list(range(0, 20))
+ random.shuffle(pids)
+
+ for i in range(0, 20):
+ pid = pids[i]
+ component = MockComponent('test' + str(pid), pid,
+ 'Test' + str(pid))
+ bob.components[pid] = component
+
+ process_list = bob.get_processes()
+ self.assertEqual(20, len(process_list))
+
+ last_pid = -1
+ for process in process_list:
+ pid = process[0]
+ self.assertLessEqual(last_pid, pid)
+ last_pid = pid
+ self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
+ process)
+
+ def _test_reap_children_helper(self, runnable, is_running, failed):
+ '''Construct a BoB instance, set various data in it according to
+ passed args and check if the component was added to the list of
+ components to restart.'''
+ bob = MockBob()
+ bob.runnable = runnable
+
+ component = MockComponent('test', 53)
+ component.running = is_running
+ component.has_failed = failed
+ bob.components[53] = component
+
+ self.assertNotIn(component, bob.components_to_restart)
+
+ bob.reap_children()
+
+ if runnable and is_running and not failed:
+ self.assertIn(component, bob.components_to_restart)
+ else:
+ self.assertEqual([], bob.components_to_restart)
+
+ def test_reap_children(self):
+ '''Test that children are queued to be restarted when they ask for it.'''
+ # test various combinations of 3 booleans
+ # (BoB.runnable, component.is_running(), component.failed())
+ self._test_reap_children_helper(False, False, False)
+ self._test_reap_children_helper(False, False, True)
+ self._test_reap_children_helper(False, True, False)
+ self._test_reap_children_helper(False, True, True)
+ self._test_reap_children_helper(True, False, False)
+ self._test_reap_children_helper(True, False, True)
+ self._test_reap_children_helper(True, True, False)
+ self._test_reap_children_helper(True, True, True)
+
+ # setup for more tests below
+ bob = MockBob()
+ bob.runnable = True
+ component = MockComponent('test', 53)
+ bob.components[53] = component
+
+ # case where the returned pid is unknown to us. nothing should
+ # happpen then.
+ bob.get_process_exit_status_called = False
+ bob._get_process_exit_status = bob._get_process_exit_status_unknown_pid
+ bob.components_to_restart = []
+ # this should do nothing as the pid is unknown
+ bob.reap_children()
+ self.assertEqual([], bob.components_to_restart)
+
+ # case where bob._get_process_exit_status() raises OSError with
+ # errno.ECHILD
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_oserror_echild
+ bob.components_to_restart = []
+ # this should catch and handle the OSError
+ bob.reap_children()
+ self.assertEqual([], bob.components_to_restart)
+
+ # case where bob._get_process_exit_status() raises OSError with
+ # errno other than ECHILD
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_oserror_other
+ with self.assertRaises(OSError):
+ bob.reap_children()
+
+ # case where bob._get_process_exit_status() raises something
+ # other than OSError
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_other
+ with self.assertRaises(Exception):
+ bob.reap_children()
+
+ def test_kill_started_components(self):
+ '''Test that started components are killed.'''
+ bob = MockBob()
+
+ component = MockComponent('test', 53, 'Test')
+ bob.components[53] = component
+
+ self.assertEqual([[53, 'test', 'Test']], bob.get_processes())
+ bob.kill_started_components()
+ self.assertEqual([], bob.get_processes())
+ self.assertTrue(component.forceful)
+
+ def _start_msgq_helper(self, bob, verbose):
+ bob.verbose = verbose
+ pi = bob.start_msgq()
+ self.assertEqual('b10-msgq', pi.name)
+ self.assertEqual(['b10-msgq'], pi.args)
+ self.assertTrue(pi.dev_null_stdout)
+ self.assertEqual(pi.dev_null_stderr, not verbose)
+ self.assertEqual({'FOO': 'an env string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_msgq(self):
+ '''Test that b10-msgq is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'FOO': 'an env string'}
+ bob._run_under_unittests = True
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ # non-verbose case
+ self._start_msgq_helper(bob, False)
+
+ # verbose case
+ self._start_msgq_helper(bob, True)
+
+ def test_start_msgq_timeout(self):
+ '''Test that b10-msgq startup attempts connections several times
+ and times out eventually.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {}
+ # set the timeout to an arbitrary pre-determined value (which
+ # code below depends on)
+ bob.msgq_timeout = 1
+ bob._run_under_unittests = False
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ global tsec
+ attempts = 0
+ tsec = 0
+ self._tmp_time = time.time
+ self._tmp_sleep = time.sleep
+ def _my_time():
+ global attempts
+ global tsec
+ attempts += 1
+ return tsec
+ def _my_sleep(nsec):
+ global tsec
+ tsec += nsec
+ time.time = _my_time
+ time.sleep = _my_sleep
+
+ global cc_sub
+ cc_sub = None
+ class DummySessionAlwaysFails():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('Connection fails')
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySessionAlwaysFails
+
+ with self.assertRaises(bind10_src.CChannelConnectError):
+ # An exception will be thrown here when it eventually times
+ # out.
+ pi = bob.start_msgq()
+
+ # time.time() should be called 12 times within the while loop:
+ # starting from 0, and 11 more times from 0.1 to 1.1. There's
+ # another call to time.time() outside the loop, which makes it
+ # 13.
+ self.assertEqual(attempts, 13)
+
+ # group_subscribe() should not have been called here.
+ self.assertIsNone(cc_sub)
+
+ global cc_socket_file
+ cc_socket_file = None
+ cc_sub = None
+ class DummySession():
+ def __init__(self, socket_file):
+ global cc_socket_file
+ cc_socket_file = socket_file
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySession
+
+ # reset values
+ attempts = 0
+ tsec = 0
+
+ pi = bob.start_msgq()
+
+ # just one attempt, but 2 calls to time.time()
+ self.assertEqual(attempts, 2)
+
+ self.assertEqual(cc_socket_file, bob.msgq_socket_file)
+ self.assertEqual(cc_sub, 'Boss')
+
+ # isc.cc.Session, time.time() and time.sleep() are restored
+ # during tearDown().
+
+ def _start_cfgmgr_helper(self, bob, data_path, filename, clear_config):
+ expect_args = ['b10-cfgmgr']
+ if data_path is not None:
+ bob.data_path = data_path
+ expect_args.append('--data-path=' + data_path)
+ if filename is not None:
+ bob.config_filename = filename
+ expect_args.append('--config-filename=' + filename)
+ if clear_config:
+ bob.clear_config = clear_config
+ expect_args.append('--clear-config')
+
+ pi = bob.start_cfgmgr()
+ self.assertEqual('b10-cfgmgr', pi.name)
+ self.assertEqual(expect_args, pi.args)
+ self.assertEqual({'TESTENV': 'A test string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_cfgmgr(self):
+ '''Test that b10-cfgmgr is started.'''
+ class DummySession():
+ def __init__(self):
+ self._tries = 0
+ def group_recvmsg(self):
+ self._tries += 1
+ # return running on the 3rd try onwards
+ if self._tries >= 3:
+ return ({'running': 'ConfigManager'}, None)
+ else:
+ return ({}, None)
+
+ bob = MockBobSimple()
+ bob.c_channel_env = {'TESTENV': 'A test string'}
+ bob.cc_session = DummySession()
+ bob.wait_time = 5
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # defaults
+ self._start_cfgmgr_helper(bob, None, None, False)
+
+ # check that 2 attempts were made. on the 3rd attempt,
+ # process_running() returns that ConfigManager is running.
+ self.assertEqual(attempts, 2)
+
+ # data_path is specified
+ self._start_cfgmgr_helper(bob, '/var/lib/test', None, False)
+
+ # config_filename is specified. Because `bob` is not
+ # reconstructed, data_path is retained from the last call to
+ # _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', False)
+
+ # clear_config is specified. Because `bob` is not reconstructed,
+ # data_path and config_filename are retained from the last call
+ # to _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', True)
+
+ def test_start_cfgmgr_timeout(self):
+ '''Test that b10-cfgmgr startup attempts connections several times
+ and times out eventually.'''
+ class DummySession():
+ def group_recvmsg(self):
+ return (None, None)
+ bob = MockBobSimple()
+ bob.c_channel_env = {}
+ bob.cc_session = DummySession()
+ # set wait_time to an arbitrary pre-determined value (which code
+ # below depends on)
+ bob.wait_time = 2
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # We just check that an exception was thrown, and that several
+ # attempts were made to connect.
+ with self.assertRaises(bind10_src.ProcessStartError):
+ pi = bob.start_cfgmgr()
+
+ # 2 seconds of attempts every 1 second should result in 2 attempts
+ self.assertEqual(attempts, 2)
+
+ # time.sleep() is restored during tearDown().
+
+ def test_start_ccsession(self):
+ '''Test that CC session is started.'''
+ class DummySession():
+ def __init__(self, specfile, config_handler, command_handler,
+ socket_file):
+ self.specfile = specfile
+ self.config_handler = config_handler
+ self.command_handler = command_handler
+ self.socket_file = socket_file
+ self.started = False
+ def start(self):
+ self.started = True
+ bob = MockBobSimple()
+ self._tmp_module_cc_session = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = DummySession
+
+ bob.start_ccsession({})
+ self.assertEqual(bind10_src.SPECFILE_LOCATION, bob.ccs.specfile)
+ self.assertEqual(bob.config_handler, bob.ccs.config_handler)
+ self.assertEqual(bob.command_handler, bob.ccs.command_handler)
+ self.assertEqual(bob.msgq_socket_file, bob.ccs.socket_file)
+ self.assertTrue(bob.ccs.started)
+
+ # isc.config.ModuleCCSession is restored during tearDown().
+
+ def test_start_process(self):
+ '''Test that processes can be started.'''
+ bob = MockBob()
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ pi = bob.start_process('Test Process', ['/bin/true'], {})
+ self.assertEqual('Test Process', pi.name)
+ self.assertEqual(['/bin/true'], pi.args)
+ self.assertEqual({}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_register_process(self):
+ '''Test that processes can be registered with BoB.'''
+ bob = MockBob()
+ component = MockComponent('test', 53, 'Test')
+
+ self.assertFalse(53 in bob.components)
+ bob.register_process(53, component)
+ self.assertTrue(53 in bob.components)
+ self.assertEqual(bob.components[53].name(), 'test')
+ self.assertEqual(bob.components[53].pid(), 53)
+ self.assertEqual(bob.components[53].address(), 'Test')
+
+ def _start_simple_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['/bin/true']
+ if verbose:
+ args.append('-v')
+
+ bob.start_simple('/bin/true')
+ self.assertEqual('/bin/true', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'TESTENV': 'A test string'}, bob.started_process_env)
+
+ def test_start_simple(self):
+ '''Test simple process startup.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'TESTENV': 'A test string'}
+
+ # non-verbose case
+ self._start_simple_helper(bob, False)
+
+ # verbose case
+ self._start_simple_helper(bob, True)
+
+ def _start_auth_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['b10-auth']
+ if verbose:
+ args.append('-v')
+
+ bob.start_auth()
+ self.assertEqual('b10-auth', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'FOO': 'an env string'}, bob.started_process_env)
+
+ def test_start_auth(self):
+ '''Test that b10-auth is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'FOO': 'an env string'}
+
+ # non-verbose case
+ self._start_auth_helper(bob, False)
+
+ # verbose case
+ self._start_auth_helper(bob, True)
+
+ def _start_resolver_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['b10-resolver']
+ if verbose:
+ args.append('-v')
+
+ bob.start_resolver()
+ self.assertEqual('b10-resolver', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'BAR': 'an env string'}, bob.started_process_env)
+
+ def test_start_resolver(self):
+ '''Test that b10-resolver is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'BAR': 'an env string'}
+
+ # non-verbose case
+ self._start_resolver_helper(bob, False)
+
+ # verbose case
+ self._start_resolver_helper(bob, True)
+
+ def _start_cmdctl_helper(self, bob, verbose, port = None):
+ bob.verbose = verbose
+
+ args = ['b10-cmdctl']
+
+ if port is not None:
+ bob.cmdctl_port = port
+ args.append('--port=9353')
+
+ if verbose:
+ args.append('-v')
+
+ bob.start_cmdctl()
+ self.assertEqual('b10-cmdctl', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'BAZ': 'an env string'}, bob.started_process_env)
+
+ def test_start_cmdctl(self):
+ '''Test that b10-cmdctl is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'BAZ': 'an env string'}
+
+ # non-verbose case
+ self._start_cmdctl_helper(bob, False)
+
+ # verbose case
+ self._start_cmdctl_helper(bob, True)
+
+ # with port, non-verbose case
+ self._start_cmdctl_helper(bob, False, 9353)
+
+ # with port, verbose case
+ self._start_cmdctl_helper(bob, True, 9353)
+
+ def test_socket_data(self):
+ '''Test that BoB._socket_data works as expected.'''
+ class MockSock:
+ def __init__(self, fd, throw):
+ self.fd = fd
+ self.throw = throw
+ self.buf = b'Hello World.\nYou are so nice today.\nXX'
+ self.i = 0
+
+ def recv(self, bufsize, flags = 0):
+ if bufsize != 1:
+ raise Exception('bufsize != 1')
+ if flags != socket.MSG_DONTWAIT:
+ raise Exception('flags != socket.MSG_DONTWAIT')
+ # after 15 recv()s, throw a socket.error with EAGAIN to
+ # get _socket_data() to save back what's been read. The
+ # number 15 is arbitrarily chosen, but the checks then
+ # depend on this being 15, i.e., if you adjust this
+ # number, you may have to adjust the checks below too.
+ if self.throw and self.i > 15:
+ raise socket.error(errno.EAGAIN, 'Try again')
+ if self.i >= len(self.buf):
+ return b'';
+ t = self.i
+ self.i += 1
+ return self.buf[t:t+1]
+
+ def close(self):
+ return
+
+ class MockBobSocketData(BoB):
+ def __init__(self, throw):
+ self._unix_sockets = {42: (MockSock(42, throw), b'')}
+ self.requests = []
+ self.dead = []
+
+ def socket_request_handler(self, previous, sock):
+ self.requests.append({sock.fd: previous})
+
+ def socket_consumer_dead(self, sock):
+ self.dead.append(sock.fd)
+
+ # Case where we get data every time we call recv()
+ bob = MockBobSocketData(False)
+ bob._socket_data(42)
+ self.assertEqual(bob.requests,
+ [{42: b'Hello World.'},
+ {42: b'You are so nice today.'}])
+ self.assertEqual(bob.dead, [42])
+ self.assertEqual({}, bob._unix_sockets)
+
+ # Case where socket.recv() raises EAGAIN. In this case, the
+ # routine is supposed to save what it has back to
+ # BoB._unix_sockets.
+ bob = MockBobSocketData(True)
+ bob._socket_data(42)
+ self.assertEqual(bob.requests, [{42: b'Hello World.'}])
+ self.assertFalse(bob.dead)
+ self.assertEqual(len(bob._unix_sockets), 1)
+ self.assertEqual(bob._unix_sockets[42][1], b'You')
+
+ def test_startup(self):
+ '''Test that BoB.startup() handles failures properly.'''
+ class MockBobStartup(BoB):
+ def __init__(self, throw):
+ self.throw = throw
+ self.started = False
+ self.killed = False
+ self.msgq_socket_file = None
+ self.curproc = 'myproc'
+ self.runnable = False
+
+ def start_all_components(self):
+ self.started = True
+ if self.throw:
+ raise Exception('Assume starting components has failed.')
+
+ def kill_started_components(self):
+ self.killed = True
+
+ class DummySession():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('This is the expected case.')
+
+ class DummySessionSocketExists():
+ def __init__(self, socket_file):
+ # simulate that connect passes
+ return
+
+ isc.cc.Session = DummySession
+
+ # All is well case, where all components are started
+ # successfully. We check that the actual call to
+ # start_all_components() is made, and BoB.runnable is true.
+ bob = MockBobStartup(False)
+ r = bob.startup()
+ self.assertIsNone(r)
+ self.assertTrue(bob.started)
+ self.assertFalse(bob.killed)
+ self.assertTrue(bob.runnable)
+ self.assertEqual({}, bob.c_channel_env)
+
+ # Case where starting components fails. We check that
+ # kill_started_components() is called right after, and
+ # BoB.runnable is not modified.
+ bob = MockBobStartup(True)
+ r = bob.startup()
+ # r contains an error message
+ self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
+ self.assertTrue(bob.started)
+ self.assertTrue(bob.killed)
+ self.assertFalse(bob.runnable)
+ self.assertEqual({}, bob.c_channel_env)
+
+ # Check if msgq_socket_file is carried over
+ bob = MockBobStartup(False)
+ bob.msgq_socket_file = 'foo'
+ r = bob.startup()
+ self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+
+ # Check the case when socket file already exists
+ isc.cc.Session = DummySessionSocketExists
+ bob = MockBobStartup(False)
+ r = bob.startup()
+ self.assertIn('already running', r)
+
+ # isc.cc.Session is restored during tearDown().
class SocketSrvTest(unittest.TestCase):
"""
@@ -1563,6 +2305,25 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
bind10_src.remove_lock_files()
+ def test_get_signame(self):
+ # just test with some samples
+ signame = bind10_src.get_signame(signal.SIGTERM)
+ self.assertEqual('SIGTERM', signame)
+ signame = bind10_src.get_signame(signal.SIGKILL)
+ self.assertEqual('SIGKILL', signame)
+ # 59426 is hopefully an unused signal on most platforms
+ signame = bind10_src.get_signame(59426)
+ self.assertEqual('Unknown signal 59426', signame)
+
+ def test_fatal_signal(self):
+ self.assertIsNone(bind10_src.boss_of_bind)
+ bind10_src.boss_of_bind = BoB()
+ bind10_src.boss_of_bind.runnable = True
+ bind10_src.fatal_signal(signal.SIGTERM, None)
+ # Now, runnable must be False
+ self.assertFalse(bind10_src.boss_of_bind.runnable)
+ bind10_src.boss_of_bind = None
+
if __name__ == '__main__':
# store os.environ for test_unchanged_environment
original_os_environ = copy.deepcopy(os.environ)
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index a3763c9..315e3c5 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -27,7 +27,7 @@ import glob
import os.path
import imp
import isc.log
-isc.log.init("b10-cfgmgr")
+isc.log.init("b10-cfgmgr", buffer=True)
from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger
from isc.log_messages.cfgmgr_messages import *
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 4076f4c..52af54a 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -49,7 +49,7 @@ from hashlib import sha1
from isc.util import socketserver_mixin
from isc.log_messages.cmdctl_messages import *
-isc.log.init("b10-cmdctl")
+isc.log.init("b10-cmdctl", buffer=True)
logger = isc.log.Logger("cmdctl")
# Debug level for communication with BIND10
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index eaeb06c..094e0ec 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -45,7 +45,7 @@ import os.path
import signal
import socket
-isc.log.init("b10-ddns")
+isc.log.init("b10-ddns", buffer=True)
logger = isc.log.Logger("ddns")
TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index e0c97d1..c896591 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -44,6 +44,7 @@ pkglibexec_PROGRAMS = b10-dhcp4
b10_dhcp4_SOURCES = main.cc
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
+b10_dhcp4_SOURCES += config_parser.cc config_parser.h
b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
@@ -57,6 +58,7 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
new file mode 100644
index 0000000..08d16f8
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.cc
@@ -0,0 +1,772 @@
+// 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 <config/ccsession.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/dhcp4_log.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief auxiliary type used for storing element name and its parser
+typedef pair<string, ConstElementPtr> ConfigPair;
+
+/// @brief a factory method that will create a parser for a given element name
+typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
+
+/// @brief a collection of factories that creates parsers for specified element names
+typedef std::map<std::string, ParserFactory*> FactoryMap;
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<Pool4Ptr> PoolStorage;
+
+/// @brief Global uint32 parameters that will be used as defaults.
+Uint32Storage uint32_defaults;
+
+/// @brief global string parameters that will be used as defaults.
+StringStorage string_defaults;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See \ref Dhcp4ConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ DebugParser(const std::string& param_name)
+ :param_name_(param_name) {
+ }
+
+ /// @brief builds parameter value
+ ///
+ /// See \ref Dhcp4ConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ virtual void build(ConstElementPtr new_config) {
+ std::cout << "Build for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ value_ = new_config;
+ }
+
+ /// @brief pretends to apply the configuration
+ ///
+ /// This is a method required by base class. It pretends to apply the
+ /// configuration, but in fact it only prints the parameter out.
+ ///
+ /// See \ref Dhcp4ConfigParser class for details.
+ virtual void commit() {
+ // Debug message. The whole DebugParser class is used only for parser
+ // debugging, and is not used in production code. It is very convenient
+ // to keep it around. Please do not turn this cout into logger calls.
+ std::cout << "Commit for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ }
+
+ /// @brief factory that constructs DebugParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new DebugParser(param_name));
+ }
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the actual value of the parameter
+ ConstElementPtr value_;
+};
+
+/// @brief Configuration parser for uint32 parameters
+///
+/// This class is a generic parser that is able to handle any uint32 integer
+/// type. By default it stores the value in external global container
+/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref Dhcp4ConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv4ConfigInherit page.
+class Uint32Parser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor for Uint32Parser
+ /// @param param_name name of the configuration parameter being parsed
+ Uint32Parser(const std::string& param_name)
+ :storage_(&uint32_defaults), param_name_(param_name) {
+ }
+
+ /// @brief builds parameter value
+ ///
+ /// Parses configuration entry and stores it in a storage. See
+ /// \ref setStorage() for details.
+ ///
+ /// @param value pointer to the content of parsed values
+ /// @throw BadValue if supplied value could not be base to uint32_t
+ virtual void build(ConstElementPtr value) {
+ int64_t check;
+ string x = value->str();
+ try {
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(BadValue, "Failed to parse value " << value->str()
+ << " as unsigned 32-bit integer.");
+ }
+ if (check > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "Value " << value->str() << "is too large"
+ << " for unsigned 32-bit integer.");
+ }
+ if (check < 0) {
+ isc_throw(BadValue, "Value " << value->str() << "is negative."
+ << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+ }
+
+ // value is small enough to fit
+ value_ = static_cast<uint32_t>(check);
+
+ (*storage_)[param_name_] = value_;
+ }
+
+ /// @brief does nothing
+ ///
+ /// This method is required for all parsers. The value itself
+ /// is not commited anywhere. Higher level parsers are expected to
+ /// use values stored in the storage, e.g. renew-timer for a given
+ /// subnet is stored in subnet-specific storage. It is not commited
+ /// here, but is rather used by \ref Subnet4ConfigParser when constructing
+ /// the subnet.
+ virtual void commit() {
+ }
+
+ /// @brief factory that constructs Uint32Parser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new Uint32Parser(param_name));
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See \ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(Uint32Storage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// pointer to the storage, where parsed value will be stored
+ Uint32Storage* storage_;
+
+ /// name of the parameter to be parsed
+ std::string param_name_;
+
+ /// the actual parsed value
+ uint32_t value_;
+};
+
+/// @brief Configuration parser for string parameters
+///
+/// This class is a generic parser that is able to handle any string
+/// parameter. By default it stores the value in external global container
+/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref Dhcp4ConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv4ConfigInherit page.
+class StringParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor for StringParser
+ /// @param param_name name of the configuration parameter being parsed
+ StringParser(const std::string& param_name)
+ :storage_(&string_defaults), param_name_(param_name) {
+ }
+
+ /// @brief parses parameter value
+ ///
+ /// Parses configuration entry and stores it in storage. See
+ /// \ref setStorage() for details.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(ConstElementPtr value) {
+ value_ = value->str();
+ boost::erase_all(value_, "\"");
+
+ (*storage_)[param_name_] = value_;
+ }
+
+ /// @brief does nothing
+ ///
+ /// This method is required for all parser. The value itself
+ /// is not commited anywhere. Higher level parsers are expected to
+ /// use values stored in the storage, e.g. renew-timer for a given
+ /// subnet is stored in subnet-specific storage. It is not commited
+ /// here, but is rather used by its parent parser when constructing
+ /// an object, e.g. the subnet.
+ virtual void commit() {
+ }
+
+ /// @brief factory that constructs StringParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new StringParser(param_name));
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See \ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(StringStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// pointer to the storage, where parsed value will be stored
+ StringStorage* storage_;
+
+ /// name of the parameter to be parsed
+ std::string param_name_;
+
+ /// the actual parsed value
+ std::string value_;
+};
+
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp4/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp4/interface parameter.
+class InterfaceListConfigParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "interface" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
+ InterfaceListConfigParser(const std::string& param_name) {
+ if (param_name != "interface") {
+ isc_throw(BadValue, "Internal error. Interface configuration "
+ "parser called for the wrong parameter: " << param_name);
+ }
+ }
+
+ /// @brief parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the interfaces list.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(ConstElementPtr value) {
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ interfaces_.push_back(iface->str());
+ }
+ }
+
+ /// @brief commits interfaces list configuration
+ virtual void commit() {
+ /// @todo: Implement per interface listening. Currently always listening
+ /// on all interfaces.
+ }
+
+ /// @brief factory that constructs InterfaceListConfigParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new InterfaceListConfigParser(param_name));
+ }
+
+private:
+ /// contains list of network interfaces
+ vector<string> interfaces_;
+};
+
+/// @brief parser for pool definition
+///
+/// This parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool4 objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// As there are no default values for pool, setStorage() must be called
+/// before build(). Otherwise exception will be thrown.
+///
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class PoolParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor.
+ PoolParser(const std::string& /*param_name*/)
+ :pools_(NULL) {
+ // ignore parameter name, it is always Dhcp4/subnet4[X]/pool
+ }
+
+ /// @brief parses the actual list
+ ///
+ /// This method parses the actual list of interfaces.
+ /// No validation is done at this stage, everything is interpreted as
+ /// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw InvalidOperation if storage was not specified (setStorage() not called)
+ /// @throw Dhcp4ConfigError when pool parsing fails
+ void build(ConstElementPtr pools_list) {
+ // setStorage() should have been called before build
+ if (!pools_) {
+ isc_throw(InvalidOperation, "Parser logic error. No pool storage set,"
+ " but pool parser asked to parse pools");
+ }
+
+ BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor.
+ len = boost::lexical_cast<int>(prefix_len);
+ } catch (...) {
+ isc_throw(Dhcp4ConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ Pool4Ptr pool(new Pool4(addr, len));
+ pools_->push_back(pool);
+ continue;
+ }
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ IOAddress min(txt.substr(0,pos));
+ IOAddress max(txt.substr(pos + 1));
+
+ Pool4Ptr pool(new Pool4(min, max));
+
+ pools_->push_back(pool);
+ continue;
+ }
+
+ isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:"
+ << text_pool->stringValue() <<
+ ". Does not contain - (for min-max) nor / (prefix/len)");
+ }
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See \ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(PoolStorage* storage) {
+ pools_ = storage;
+ }
+
+ /// @brief does nothing.
+ ///
+ /// This method is required for all parsers. The value itself
+ /// is not commited anywhere. Higher level parsers (for subnet) are expected
+ /// to use values stored in the storage.
+ virtual void commit() {}
+
+ /// @brief factory that constructs PoolParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new PoolParser(param_name));
+ }
+
+private:
+ /// @brief pointer to the actual Pools storage
+ ///
+ /// That is typically a storage somewhere in Subnet parser
+ /// (an upper level parser).
+ PoolStorage* pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class Subnet4ConfigParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor
+ Subnet4ConfigParser(const std::string& ) {
+ // The parameter should always be "subnet", but we don't check here
+ // against it in case someone wants to reuse this parser somewhere.
+ }
+
+ /// @brief parses parameter value
+ ///
+ /// @param subnet pointer to the content of subnet definition
+ void build(ConstElementPtr subnet) {
+
+ BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+
+ ParserPtr parser(createSubnet4ConfigParser(param.first));
+
+ // if this is an Uint32 parser, tell it to store the values
+ // in values_, rather than in global storage
+ boost::shared_ptr<Uint32Parser> uint_parser =
+ boost::dynamic_pointer_cast<Uint32Parser>(parser);
+ if (uint_parser) {
+ uint_parser->setStorage(&uint32_values_);
+ } else {
+
+ boost::shared_ptr<StringParser> string_parser =
+ boost::dynamic_pointer_cast<StringParser>(parser);
+ if (string_parser) {
+ string_parser->setStorage(&string_values_);
+ } else {
+
+ boost::shared_ptr<PoolParser> pool_parser =
+ boost::dynamic_pointer_cast<PoolParser>(parser);
+ if (pool_parser) {
+ pool_parser->setStorage(&pools_);
+ }
+ }
+ }
+
+ parser->build(param.second);
+ parsers_.push_back(parser);
+ }
+
+ // Ok, we now have subnet parsed
+ }
+
+ /// @brief commits received configuration.
+ ///
+ /// This method does most of the configuration. Many other parsers are just
+ /// storing the values that are actually consumed here. Pool definitions
+ /// created in other parsers are used here and added to newly created Subnet4
+ /// objects. Subnet4 are then added to DHCP CfgMgr.
+ /// @throw Dhcp4ConfigError if there are any issues encountered during commit
+ void commit() {
+
+ StringStorage::const_iterator it = string_values_.find("subnet");
+ if (it == string_values_.end()) {
+ isc_throw(Dhcp4ConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+ string subnet_txt = it->second;
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(Dhcp4ConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << it->second);
+ }
+ IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ Triplet<uint32_t> t1 = getParam("renew-timer");
+ Triplet<uint32_t> t2 = getParam("rebind-timer");
+ Triplet<uint32_t> valid = getParam("valid-lifetime");
+
+ /// @todo: Convert this to logger once the parser is working reliably
+ stringstream tmp;
+ tmp << addr.toText() << "/" << (int)len
+ << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
+
+ Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+
+ for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+ subnet->addPool4(*it);
+ }
+
+ CfgMgr::instance().addSubnet4(subnet);
+ }
+
+private:
+
+ /// @brief creates parsers for entries in subnet definition
+ ///
+ /// @todo Add subnet-specific things here (e.g. subnet-specific options)
+ ///
+ /// @param config_id name od the entry
+ /// @return parser object for specified entry name
+ /// @throw NotImplemented if trying to create a parser for unknown config element
+ Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ factories["valid-lifetime"] = Uint32Parser::Factory;
+ factories["renew-timer"] = Uint32Parser::Factory;
+ factories["rebind-timer"] = Uint32Parser::Factory;
+ factories["subnet"] = StringParser::Factory;
+ factories["pool"] = PoolParser::Factory;
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DebugParser(config_id);
+
+ isc_throw(NotImplemented,
+ "Parser error: Subnet4 parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+ }
+
+ /// @brief returns value for a given parameter (after using inheritance)
+ ///
+ /// This method implements inheritance. For a given parameter name, it first
+ /// checks if there is a global value for it and overwrites it with specific
+ /// value if such value was defined in subnet.
+ ///
+ /// @param name name of the parameter
+ /// @return triplet with the parameter name
+ /// @throw Dhcp4ConfigError when requested parameter is not present
+ Triplet<uint32_t> getParam(const std::string& name) {
+ uint32_t value = 0;
+ bool found = false;
+ Uint32Storage::iterator global = uint32_defaults.find(name);
+ if (global != uint32_defaults.end()) {
+ value = global->second;
+ found = true;
+ }
+
+ Uint32Storage::iterator local = uint32_values_.find(name);
+ if (local != uint32_values_.end()) {
+ value = local->second;
+ found = true;
+ }
+
+ if (found) {
+ return (Triplet<uint32_t>(value));
+ } else {
+ isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ /// storage for subnet-specific uint32 values
+ Uint32Storage uint32_values_;
+
+ /// storage for subnet-specific integer values
+ StringStorage string_values_;
+
+ /// storage for pools belonging to this subnet
+ PoolStorage pools_;
+
+ /// parsers are stored here
+ ParserCollection parsers_;
+};
+
+/// @brief this class parses list of subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet4
+/// definitions. It iterates over all entries and creates Subnet4ConfigParser
+/// for each entry.
+class Subnets4ListConfigParser : public Dhcp4ConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ Subnets4ListConfigParser(const std::string&) {
+ /// parameter name is ignored
+ }
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list and creates Subnet4ConfigParser
+ /// for each entry.
+ ///
+ /// @param subnets_list pointer to a list of IPv4 subnets
+ void build(ConstElementPtr subnets_list) {
+
+ // No need to define FactoryMap here. There's only one type
+ // used: Subnet4ConfigParser
+
+ BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+
+ ParserPtr parser(new Subnet4ConfigParser("subnet"));
+ parser->build(subnet);
+ subnets_.push_back(parser);
+ }
+
+ }
+
+ /// @brief commits subnets definitions.
+ ///
+ /// Iterates over all Subnet4 parsers. Each parser contains definitions
+ /// of a single subnet and its parameters and commits each subnet separately.
+ void commit() {
+ // @todo: Implement more subtle reconfiguration than toss
+ // the old one and replace with the new one.
+
+ // remove old subnets
+ CfgMgr::instance().deleteSubnets4();
+
+ BOOST_FOREACH(ParserPtr subnet, subnets_) {
+ subnet->commit();
+ }
+
+ }
+
+ /// @brief Returns Subnet4ListConfigParser object
+ /// @param param_name name of the parameter
+ /// @return Subnets4ListConfigParser object
+ static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new Subnets4ListConfigParser(param_name));
+ }
+
+ /// @brief collection of subnet parsers.
+ ParserCollection subnets_;
+};
+
+/// @brief creates global parsers
+///
+/// This method creates global parsers that parse global parameters, i.e.
+/// those that take format of Dhcp4/param1, Dhcp4/param2 and so forth.
+///
+/// @param config_id pointer to received global configuration entry
+/// @return parser for specified global DHCPv4 parameter
+/// @throw NotImplemented if trying to create a parser for unknown config element
+Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ factories["valid-lifetime"] = Uint32Parser::Factory;
+ factories["renew-timer"] = Uint32Parser::Factory;
+ factories["rebind-timer"] = Uint32Parser::Factory;
+ factories["interface"] = InterfaceListConfigParser::Factory;
+ factories["subnet4"] = Subnets4ListConfigParser::Factory;
+ factories["version"] = StringParser::Factory;
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DebugParser(config_id);
+
+ isc_throw(NotImplemented,
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+}
+
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
+ if (!config_set) {
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ /// @todo: append most essential info here (like "2 new subnets configured")
+ string config_details;
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
+
+ ParserCollection parsers;
+ try {
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parsers.push_back(parser);
+ }
+ } catch (const isc::Exception& ex) {
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed:") + ex.what());
+ return (answer);
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
+ }
+
+ try {
+ BOOST_FOREACH(ParserPtr parser, parsers) {
+ parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ ConstElementPtr answer = isc::config::createAnswer(2,
+ string("Configuration commit failed:") + ex.what());
+ return (answer);
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ ConstElementPtr answer = isc::config::createAnswer(2,
+ string("Configuration commit failed"));
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
+
+ ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ return (answer);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
new file mode 100644
index 0000000..2ee9cf6
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.h
@@ -0,0 +1,167 @@
+// 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 <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <string>
+
+#ifndef DHCP4_CONFIG_PARSER_H
+#define DHCP4_CONFIG_PARSER_H
+
+/// @todo: This header file and its .cc counterpart are very similar between
+/// DHCPv4 and DHCPv6. They should be merged. A ticket #2355.
+
+namespace isc {
+namespace dhcp {
+
+class Dhcpv4Srv;
+
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c Dhcpv4Srv object.
+class Dhcp4ConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ Dhcp4ConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Base abstract class for all DHCPv4 parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
+class Dhcp4ConfigParser {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+
+ // Private construtor and assignment operator assures that nobody
+ // will be able to copy or assign a parser. There are no defined
+ // bodies for them.
+ Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
+ Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is intentionally defined as \c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ Dhcp4ConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~Dhcp4ConfigParser() {}
+ //@}
+
+ /// \brief Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of \c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// \c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of \c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once in the
+ /// life of the object. Although multiple calls are not prohibited
+ /// by the interface, the behavior is undefined.
+ ///
+ /// \param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// \brief Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in \c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via \c configureDhcp4Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after \c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+};
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<Dhcp4ConfigParser> ParserPtr;
+
+/// @brief a collection of parsers
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+
+/// \brief Configure DHCPv4 server (\c Dhcpv4Srv) with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function returns appropriate error code.
+///
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv4 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
+///
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - logical error (parsing was successful, but the values are invalid)
+///
+/// @param config_set a new configuration (JSON) for DHCPv4 server
+/// @return answer that contains result of reconfiguration
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv&,
+ isc::data::ConstElementPtr config_set);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP4_CONFIG_PARSER_H
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index b02bf72..eb5d482 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -22,8 +22,11 @@
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
+#include <dhcp4/config_parser.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
+#include <cassert>
+#include <iostream>
#include <cassert>
#include <iostream>
@@ -46,8 +49,14 @@ ConstElementPtr
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
.arg(new_config->str());
- ConstElementPtr answer = isc::config::createAnswer(0,
- "Thank you for sending config.");
+ if (server_) {
+ return (configureDhcp4Server(*server_, new_config));
+ }
+
+ // That should never happen as we install config_handler after we instantiate
+ // the server.
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ "Configuration rejected, server is during startup/shutdown phase.");
return (answer);
}
@@ -100,10 +109,22 @@ void ControlledDhcpv4Srv::establishSession() {
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
config_session_ = new ModuleCCSession(specfile, *cc_session_,
- dhcp4ConfigHandler,
+ NULL,
dhcp4CommandHandler, false);
config_session_->start();
+ // We initially create ModuleCCSession() without configHandler, as
+ // the session module is too eager to send partial configuration.
+ // We want to get the full configuration, so we explicitly call
+ // getFullConfig() and then pass it to our configHandler.
+ config_session_->setConfigHandler(dhcp4ConfigHandler);
+
+ try {
+ configureDhcp4Server(*this, config_session_->getFullConfig());
+ } catch (const Dhcp4ConfigError& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+ }
+
/// Integrate the asynchronous I/O model of BIND 10 configuration
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv4Session.
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 5f45077..9bd261c 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -68,6 +68,8 @@ public:
///
/// @param command Text represenation of the command (e.g. "shutdown")
/// @param args Optional parameters
+ /// @param command text represenation of the command (e.g. "shutdown")
+ /// @param args optional parameters
///
/// @return status of the command
static isc::data::ConstElementPtr
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
new file mode 100644
index 0000000..05f1670
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -0,0 +1,68 @@
+/**
+ @page dhcp4 DHCPv4 Server Component
+
+BIND10 offers DHCPv4 server implementation. It is implemented as
+b10-dhcp4 component. Its primary code is located in
+isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively,
+especially isc::dhcp::Pkt4, isc::dhcp::Option and
+isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+functionality, i.e. it is able to receive and process incoming
+requests and trasmit responses. However, it does not have database
+management, so it returns only one, hardcoded lease to whoever asks
+for it.
+
+DHCPv4 server component does not support direct traffic (relayed
+only), as support for transmission to hosts without IPv4 address
+assigned is not implemented in IfaceMgr yet.
+
+ at section dhcpv4Session BIND10 message queue integration
+
+DHCPv4 server component is now integrated with BIND10 message queue.
+The integration is performed by establishSession() and disconnectSession()
+functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
+in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
+class that establishes connection with msgq and install necessary handlers
+for receiving commands and configuration updates. It is derived from
+a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality,
+without any controlling mechanisms.
+
+ControlledDhcpv4Srv instantiates several components to make management
+session possible. In particular, isc::cc::Session cc_session
+object uses ASIO for establishing connection. It registers its socket
+in isc::asiolink::IOService io_service object. Typically, other components
+(e.g. auth or resolver) that use ASIO for their communication, register their
+other sockets in the
+same io_service and then just call io_service.run() method that does
+not return, until one of the callback decides that it is time to shut down
+the whole component cal calls io_service.stop(). DHCPv4 works in a
+different way. It does receive messages using select()
+(see isc::dhcp::IfaceMgr::receive4()), which is incompatible with ASIO.
+To solve this problem, socket descriptor is extracted from cc_session
+object and is passed to IfaceMgr by using isc::dhcp::IfaceMgr::set_session_socket().
+IfaceMgr then uses this socket in its select() call. If there is some
+data to be read, it calls registered callback that is supposed to
+read and process incoming data.
+
+This somewhat complicated approach is needed for a simple reason. In
+embedded deployments there will be no message queue. Not referring directly
+to anything related to message queue in isc::dhcp::Dhcpv4Srv and
+isc::dhcp::IfaceMgr classes brings in two benefits. First, the can
+be used with and without message queue. Second benefit is related to the
+first one: \ref libdhcp is supposed to be simple and robust and not require
+many dependencies. One notable example of a use case that benefits from
+this approach is a perfdhcp tool. Finally, the idea is that it should be
+possible to instantiate Dhcpv4Srv object directly, thus getting a server
+that does not support msgq. That is useful for embedded environments.
+It may also be useful in validation.
+
+ at section dhcpv4ConfigParser Configuration Parser in DHCPv4
+
+This parser follows exactly the same logic as its DHCPv6 counterpart.
+See \ref dhcpv6ConfigParser.
+
+ at section dhcpv4ConfigInherit DHCPv4 configuration inheritance
+
+Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
+counterpart. See \ref dhcpv6ConfigInherit.
+
+*/
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 7584b48..d5066e8 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -4,9 +4,85 @@
"module_description": "DHCPv4 server daemon",
"config_data": [
{ "item_name": "interface",
- "item_type": "string",
+ "item_type": "list",
"item_optional": false,
- "item_default": "eth0"
+ "item_default": [ "all" ],
+ "list_item_spec":
+ {
+ "item_name": "interface_name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "all"
+ }
+ } ,
+
+ { "item_name": "renew-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1000
+ },
+
+ { "item_name": "rebind-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 2000
+ },
+
+ { "item_name": "valid-lifetime",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 4000
+ },
+
+ { "item_name": "subnet4",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-subnet4",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+
+ { "item_name": "subnet",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "renew-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1000
+ },
+
+ { "item_name": "rebind-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 2000
+ },
+
+ { "item_name": "valid-lifetime",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 7200
+ },
+ { "item_name": "pool",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ]
+ }
}
],
"commands": [
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 392b332..994f004 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,10 +26,30 @@ to establish a session with the BIND 10 control channel.
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv4 DHCP server.
+% DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial DHCPv4
+configuration has failed. The server will start, but nothing will be
+served until the configuration has been corrected.
+
% DHCP4_CONFIG_UPDATE updated configuration received: %1
A debug message indicating that the IPv4 DHCP server has received an
updated configuration from the BIND 10 configuration system.
+% DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives a
+configuration. That happens at start up and also when a server configuration
+change is committed by the administrator.
+
+% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. it is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc
index bd3be1b..45f1de1 100644
--- a/src/bin/dhcp4/main.cc
+++ b/src/bin/dhcp4/main.cc
@@ -17,6 +17,7 @@
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <boost/lexical_cast.hpp>
@@ -93,9 +94,10 @@ main(int argc, char* argv[]) {
}
// Initialize logging. If verbose, we'll use maximum verbosity.
+ // If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(DHCP4_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -112,6 +114,10 @@ main(int argc, char* argv[]) {
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
// Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
+ // We do need to make sure logging is no longer buffered
+ // since then it would not print until dhcp6 is stopped
+ isc::log::LoggerManager log_manager;
+ log_manager.process();
}
} else {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index ddc3000..e601919 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -49,9 +49,11 @@ TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
+dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
+dhcp4_unittests_SOURCES += config_parser_unittest.cc
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
if USE_CLANGPP
@@ -65,6 +67,7 @@ dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp4_unittests_LDADD = $(GTEST_LDADD)
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
new file mode 100644
index 0000000..a4ccabd
--- /dev/null
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -0,0 +1,294 @@
+// 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 <config.h>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/config_parser.h>
+#include <config/ccsession.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <limits.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace isc {
+namespace dhcp {
+extern Uint32Storage uint32_defaults;
+}
+}
+
+namespace {
+
+class Dhcp4ParserTest : public ::testing::Test {
+public:
+ Dhcp4ParserTest()
+ :rcode_(-1) {
+ // Open port 0 means to not do anything at all. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+ srv_ = new Dhcpv4Srv(0);
+ }
+
+ // Checks if global parameter of name have expected_value
+ void checkGlobalUint32(string name, uint32_t expected_value) {
+ Uint32Storage::const_iterator it = uint32_defaults.find(name);
+ if (it == uint32_defaults.end()) {
+ ADD_FAILURE() << "Expected uint32 with name " << name
+ << " not found";
+ return;
+ }
+ EXPECT_EQ(expected_value, it->second);
+ }
+
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
+ ~Dhcp4ParserTest() {
+ delete srv_;
+ };
+
+ Dhcpv4Srv* srv_;
+
+ int rcode_;
+ ConstElementPtr comment_;
+};
+
+// Goal of this test is a verification if a very simple config update
+// with just a bumped version number. That's the simplest possible
+// config update.
+TEST_F(Dhcp4ParserTest, version) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0}")));
+
+ // returned value must be 0 (configuration accepted)
+ checkResult(x, 0);
+}
+
+/// The goal of this test is to verify that the code accepts only
+/// valid commands and malformed or unsupported parameters are rejected.
+TEST_F(Dhcp4ParserTest, bogusCommand) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"bogus\": 5}")));
+
+ // returned value must be 1 (configuration parse error)
+ checkResult(x, 1);
+}
+
+/// The goal of this test is to verify if wrongly defined subnet will
+/// be rejected. Properly defined subnet must include at least one
+/// pool definition.
+TEST_F(Dhcp4ParserTest, emptySubnet) {
+
+ ConstElementPtr status;
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ ], "
+ "\"valid-lifetime\": 4000 }")));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ checkGlobalUint32("rebind-timer", 2000);
+ checkGlobalUint32("renew-timer", 1000);
+ checkGlobalUint32("valid-lifetime", 4000);
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Now check if the configuration was indeed handled and we have
+ // expected pool configured.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp4ParserTest, subnetLocal) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"valid-lifetime\": 4,"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1, subnet->getT1());
+ EXPECT_EQ(2, subnet->getT2());
+ EXPECT_EQ(4, subnet->getValid());
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.4.0/28\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value must be 2 (values error)
+ // as the pool does not belong to that subnet
+ checkResult(status, 2);
+}
+
+// Goal of this test is to verify if pools can be defined
+// using prefix/length notation. There is no separate test for min-max
+// notation as it was tested in several previous tests.
+TEST_F(Dhcp4ParserTest, poolPrefixLen) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.128/28\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value must be 0 (configuration accepted)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+}
+
+/// This test checks if Uint32Parser can really parse the whole range
+/// and properly err of out of range values. As we can't call Uint32Parser
+/// directly, we are exploiting the fact that it is used to parse global
+/// parameter renew-timer and the results are stored in uint32_defaults.
+TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
+
+ ConstElementPtr status;
+
+ // CASE 1: 0 - minimum value, should work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 0}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ checkGlobalUint32("renew-timer", 0);
+
+ // CASE 2: 4294967295U (UINT_MAX) should work as well
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 4294967295}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ checkGlobalUint32("renew-timer", 4294967295U);
+
+ // CASE 3: 4294967296U (UINT_MAX + 1) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 4294967296}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+
+ // CASE 4: -1 (UINT_MIN -1 ) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": -1}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+}
+
+};
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 2038d12..4c6fab1 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -129,7 +129,7 @@ public:
return (new DebugParser(param_name));
}
-protected:
+private:
/// name of the parsed parameter
std::string param_name_;
@@ -147,7 +147,7 @@ protected:
/// in its base class, \ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6-config-inherit page.
+/// \ref dhcpv6ConfigInherit page.
///
/// @todo this class should be turned into the template class which
/// will handle all uintX_types of data (see ticket #2415).
@@ -163,7 +163,7 @@ public:
/// @brief builds parameter value
///
/// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// \ref Uint32Parser::setStorage() for details.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
@@ -222,14 +222,14 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See \ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(Uint32Storage* storage) {
storage_ = storage;
}
-protected:
+private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
@@ -250,7 +250,7 @@ protected:
/// in its base class, \ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6-config-inherit page.
+/// \ref dhcpv6ConfigInherit page.
class StringParser : public DhcpConfigParser {
public:
@@ -269,6 +269,7 @@ public:
virtual void build(ConstElementPtr value) {
value_ = value->str();
boost::erase_all(value_, "\"");
+
// If a given parameter already exists in the storage we override
// its value. If it doesn't we insert a new element.
(*storage_)[param_name_] = value_;
@@ -293,14 +294,14 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See \ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(StringStorage* storage) {
storage_ = storage;
}
-protected:
+private:
/// pointer to the storage, where parsed value will be stored
StringStorage* storage_;
@@ -329,9 +330,10 @@ public:
/// "interface" parameter only. All other types will throw exception.
///
/// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
InterfaceListConfigParser(const std::string& param_name) {
if (param_name != "interface") {
- isc_throw(NotImplemented, "Internal error. Interface configuration "
+ isc_throw(BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -361,7 +363,7 @@ public:
return (new InterfaceListConfigParser(param_name));
}
-protected:
+private:
/// contains list of network interfaces
vector<string> interfaces_;
};
@@ -390,6 +392,8 @@ public:
/// This method parses the actual list of interfaces.
/// No validation is done at this stage, everything is interpreted as
/// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw BadValue if storage was not specified (setStorage() not called)
void build(ConstElementPtr pools_list) {
// setStorage() should have been called before build
if (!pools_) {
@@ -442,7 +446,7 @@ public:
pos = txt.find("-");
if (pos != string::npos) {
// using min-max notation
- IOAddress min(txt.substr(0,pos - 1));
+ IOAddress min(txt.substr(0, pos));
IOAddress max(txt.substr(pos + 1));
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
@@ -459,7 +463,7 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See \ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(PoolStorage* storage) {
@@ -480,7 +484,7 @@ public:
return (new PoolParser(param_name));
}
-protected:
+private:
/// @brief pointer to the actual Pools storage
///
/// This is typically a storage somewhere in Subnet parser
@@ -1005,6 +1009,7 @@ private:
///
/// @param config_id name od the entry
/// @return parser object for specified entry name
+ /// @throw NotImplemented if trying to create a parser for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
@@ -1047,6 +1052,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
+ /// @throw Dhcp6ConfigError when requested parameter is not present
Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -1156,6 +1162,7 @@ public:
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv6 parameter
+/// @throw NotImplemented if trying to create a parser for unknown config element
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
FactoryMap factories;
@@ -1205,11 +1212,13 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
///
/// @param config_set a new configuration for DHCPv6 server
/// @return answer that contains result of reconfiguration
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
if (!config_set) {
- isc_throw(Dhcp6ConfigError,
- "Null pointer is passed to configuration parser");
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
}
/// @todo: append most essential info here (like "2 new subnets configured")
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 9f7c3ae..ed44bb9 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -15,9 +15,11 @@
#ifndef DHCP6_CONFIG_PARSER_H
#define DHCP6_CONFIG_PARSER_H
+/// @todo: This header file and its .cc counterpart are very similar between
+/// DHCPv4 and DHCPv6. They should be merged. See ticket #2355.
+
#include <cc/data.h>
#include <exceptions/exceptions.h>
-
#include <string>
namespace isc {
@@ -30,15 +32,22 @@ class Dhcpv6Srv;
class Dhcp6ConfigError : public isc::Exception {
public:
-/// @brief constructor
-///
-/// @param file name of the file, where exception occurred
-/// @param line line of the file, where exception occurred
-/// @param what text description of the issue that caused exception
-Dhcp6ConfigError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ Dhcp6ConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
};
+/// @brief Base abstract class for all DHCPv6 parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
class DhcpConfigParser {
///
/// \name Constructors and Destructor
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index ba0fea9..5350fd8 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -1,5 +1,5 @@
/**
- @page dhcpv6 DHCPv6 Server Component
+ @page dhcp6 DHCPv6 Server Component
BIND10 offers DHCPv6 server implementation. It is implemented as
b10-dhcp6 component. Its primary code is located in
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6b82344..6ab42b3 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -22,6 +22,11 @@ successfully established a session with the BIND 10 control channel.
This debug message is issued just before the IPv6 DHCP server attempts
to establish a session with the BIND 10 control channel.
+% DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
+This error message indicates that the received message is being dropped
+because it does not include the mandatory client-id option necessary for
+address assignment. The most likely cause is a problem with the client.
+
% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv6 DHCP server.
@@ -81,6 +86,13 @@ This message indicates that the server failed to grant (in response to
received REQUEST) a lease for a given client. There may be many reasons for
such failure. Each specific failure is logged in a separate log entry.
+% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
+This message indicates that received DHCPv6 packet is invalid. This may be due
+to a number of reasons, e.g. the mandatory client-id option is missing,
+the server-id forbidden in that particular type of message is present,
+there is more than one instance of client-id or server-id present,
+etc. The exact reason for rejecting the packet is included in the message.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -189,3 +201,18 @@ which the DHCPv6 server has not been configured. The cause is most likely due
to a misconfiguration of the server. The packet processing will continue, but
the response will only contain generic configuration parameters and no
addresses or prefixes.
+
+% DHCP6_UNKNOWN_RENEW received RENEW from client (duid=%1, iaid=%2) in subnet %3
+This warning message is printed when client attempts to renew a lease,
+but no such lease is known by the server. It typically means that
+client has attempted to use its lease past its lifetime: causes of this
+include a adjustment of the clients date/time setting or poor support
+for sleep/recovery. A properly implemented client will recover from such
+a situation by restarting the lease allocation process after receiving
+a negative reply from the server.
+
+An alternative cause could be that the server has lost its database
+recently and does not recognize its well-behaving clients. This is more
+probable if you see many such messages. Clients will recover from this,
+but they will most likely get a different IP addresses and experience
+a brief service interruption.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 02c0dd0..2482833 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -24,6 +24,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
@@ -126,50 +127,57 @@ bool Dhcpv6Srv::run() {
continue;
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
- .arg(serverReceivedPacketName(query->getType()));
+ .arg(query->getName());
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
.arg(static_cast<int>(query->getType()))
.arg(query->getBuffer().getLength())
.arg(query->toText());
- switch (query->getType()) {
- case DHCPV6_SOLICIT:
- rsp = processSolicit(query);
- break;
+ try {
+ switch (query->getType()) {
+ case DHCPV6_SOLICIT:
+ rsp = processSolicit(query);
+ break;
- case DHCPV6_REQUEST:
- rsp = processRequest(query);
- break;
+ case DHCPV6_REQUEST:
+ rsp = processRequest(query);
+ break;
- case DHCPV6_RENEW:
- rsp = processRenew(query);
- break;
+ case DHCPV6_RENEW:
+ rsp = processRenew(query);
+ break;
- case DHCPV6_REBIND:
- rsp = processRebind(query);
- break;
+ case DHCPV6_REBIND:
+ rsp = processRebind(query);
+ break;
- case DHCPV6_CONFIRM:
- rsp = processConfirm(query);
- break;
+ case DHCPV6_CONFIRM:
+ rsp = processConfirm(query);
+ break;
- case DHCPV6_RELEASE:
+ case DHCPV6_RELEASE:
rsp = processRelease(query);
break;
- case DHCPV6_DECLINE:
- rsp = processDecline(query);
- break;
+ case DHCPV6_DECLINE:
+ rsp = processDecline(query);
+ break;
- case DHCPV6_INFORMATION_REQUEST:
- rsp = processInfRequest(query);
- break;
+ case DHCPV6_INFORMATION_REQUEST:
+ rsp = processInfRequest(query);
+ break;
- default:
- // Only action is to output a message if debug is enabled,
- // and that will be covered by the debug statement before
- // the "switch" statement.
- ;
+ default:
+ // Only action is to output a message if debug is enabled,
+ // and that will be covered by the debug statement before
+ // the "switch" statement.
+ ;
+ }
+ } catch (const RFCViolation& e) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+ .arg(query->getName())
+ .arg(query->getRemoteAddr())
+ .arg(e.what());
}
if (rsp) {
@@ -195,9 +203,6 @@ bool Dhcpv6Srv::run() {
}
}
}
-
- // TODO add support for config session (see src/bin/auth/main.cc)
- // so this daemon can be controlled from bob
}
return (true);
@@ -348,13 +353,77 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
}
OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
+ // @todo This function uses OptionCustom class to manage contents
+ // of the data fields. Since this this option is frequently used
+ // it may be good to implement dedicated class to avoid performance
+ // impact.
+
+ // Get the definition of the option holding status code.
+ OptionDefinitionPtr status_code_def =
+ LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
+ // This definition is assumed to be initialized in LibDHCP.
+ assert(status_code_def);
+
+ // As there is no dedicated class to represent Status Code
+ // the OptionCustom class should be returned here.
+ boost::shared_ptr<OptionCustom> option_status =
+ boost::dynamic_pointer_cast<
+ OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+ assert(option_status);
+
+ // Set status code to 'code' (0 - means data field #0).
+ option_status->writeInteger(code, 0);
+ // Set a message (1 - means data field #1).
+ option_status->writeString(text, 1);
+ return (option_status);
+}
+
+void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+ RequirementLevel serverid) {
+ Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
+ switch (clientid) {
+ case MANDATORY:
+ if (client_ids.size() != 1) {
+ isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
+ << pkt->getName() << ", but " << client_ids.size()
+ << " received");
+ }
+ break;
+ case OPTIONAL:
+ if (client_ids.size() > 1) {
+ isc_throw(RFCViolation, "Too many (" << client_ids.size()
+ << ") client-id options received in " << pkt->getName());
+ }
+ break;
+
+ case FORBIDDEN:
+ // doesn't make sense - client-id is always allowed
+ break;
+ }
- // @todo: Implement Option6_StatusCode and rewrite this code here
- vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
- data.insert(data.begin(), static_cast<uint8_t>(code % 256));
- data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
- OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
- return (status);
+ Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_ids.size() > 0) {
+ isc_throw(RFCViolation, "Exactly 1 server-id option expected, but "
+ << server_ids.size() << " received in " << pkt->getName());
+ }
+ break;
+
+ case MANDATORY:
+ if (server_ids.size() != 1) {
+ isc_throw(RFCViolation, "Invalid number of server-id options received ("
+ << server_ids.size() << "), exactly 1 expected in message "
+ << pkt->getName());
+ }
+ break;
+
+ case OPTIONAL:
+ if (server_ids.size() > 1) {
+ isc_throw(RFCViolation, "Too many (" << server_ids.size()
+ << ") server-id options received in " << pkt->getName());
+ }
+ }
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
@@ -370,7 +439,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(question);
- if (subnet) {
+ if (!subnet) {
// This particular client is out of luck today. We do not have
// information about the subnet he is connected to. This likely means
// misconfiguration of the server (or some relays). We will continue to
@@ -378,12 +447,13 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// addresses or prefixes, no subnet specific configuration etc. The only
// thing this client can get is some global information (like DNS
// servers).
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
- .arg(subnet->toText());
- } else {
+
// perhaps this should be logged on some higher level? This is most likely
// configuration bug.
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
}
// @todo: We should implement Option6Duid some day, but we can do without it
@@ -397,6 +467,10 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
if (opt_duid) {
duid = DuidPtr(new DUID(opt_duid->getData()));
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
+ // Let's drop the message. This client is not sane.
+ isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
}
// Now that we have all information about the client, let's iterate over all
@@ -409,7 +483,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = handleIA_NA(subnet, duid, question,
+ OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
answer->addOption(answer_opt);
@@ -422,14 +496,18 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
-OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, Pkt6Ptr question,
- boost::shared_ptr<Option6IA> ia) {
+OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
// If there is no subnet selected for handling this IA_NA, the only thing to do left is
// to say that we are sorry, but the user won't get an address. As a convenience, we
// use a different status text to indicate that (compare to the same status code,
// but different wording below)
if (!subnet) {
// Create empty IA_NA option with IAID matching the request.
+ // Note that we don't use OptionDefinition class to create this option.
+ // This is because we prefer using a constructor of Option6IA that
+ // initializes IAID. Otherwise we would have to use setIAID() after
+ // creation of the option which has some performance implications.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
@@ -471,6 +549,8 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
hint, fake_allocation);
// Create IA_NA that we will put in the response.
+ // Do not use OptionDefinition to create option's instance so
+ // as we can initialize IAID using a constructor.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
if (lease) {
@@ -512,8 +592,111 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
return (ia_rsp);
}
+OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(*duid, ia->getIAID(),
+ subnet->getID());
+
+ if (!lease) {
+ // client renewing a lease that we don't know about.
+
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ "Sorry, no known leases for this duid/iaid."));
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
+ .arg(duid->toText())
+ .arg(ia->getIAID())
+ .arg(subnet->toText());
+
+ return (ia_rsp);
+ }
+
+ lease->preferred_lft_ = subnet->getPreferred();
+ lease->valid_lft_ = subnet->getValid();
+ lease->t1_ = subnet->getT1();
+ lease->t2_ = subnet->getT2();
+ lease->cltt_ = time(NULL);
+
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ ia_rsp->setT1(subnet->getT1());
+ ia_rsp->setT2(subnet->getT2());
+
+ boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_, lease->preferred_lft_,
+ lease->valid_lft_));
+ ia_rsp->addOption(addr);
+ return (ia_rsp);
+}
+
+void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
+
+ // We need to renew addresses for all IA_NA options in the client's
+ // RENEW message.
+
+ // We need to select a subnet the client is connected in.
+ Subnet6Ptr subnet = selectSubnet(renew);
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
+ }
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = renew->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ for (Option::OptionCollection::iterator opt = renew->options_.begin();
+ opt != renew->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+
+
+}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
+ sanityCheck(solicit, MANDATORY, FORBIDDEN);
+
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
@@ -526,6 +709,9 @@ Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
}
Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
+
+ sanityCheck(request, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
copyDefaultOptions(request, reply);
@@ -538,8 +724,17 @@ Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
}
Pkt6Ptr Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
- /// @todo: Implement this
+
+ sanityCheck(renew, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
+
+ copyDefaultOptions(renew, reply);
+ appendDefaultOptions(renew, reply);
+ appendRequestedOptions(renew, reply);
+
+ renewLeases(renew, reply);
+
return reply;
}
@@ -573,48 +768,5 @@ Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
return reply;
}
-const char*
-Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
- static const char* CONFIRM = "CONFIRM";
- static const char* DECLINE = "DECLINE";
- static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
- static const char* REBIND = "REBIND";
- static const char* RELEASE = "RELEASE";
- static const char* RENEW = "RENEW";
- static const char* REQUEST = "REQUEST";
- static const char* SOLICIT = "SOLICIT";
- static const char* UNKNOWN = "UNKNOWN";
-
- switch (type) {
- case DHCPV6_CONFIRM:
- return (CONFIRM);
-
- case DHCPV6_DECLINE:
- return (DECLINE);
-
- case DHCPV6_INFORMATION_REQUEST:
- return (INFORMATION_REQUEST);
-
- case DHCPV6_REBIND:
- return (REBIND);
-
- case DHCPV6_RELEASE:
- return (RELEASE);
-
- case DHCPV6_RENEW:
- return (RENEW);
-
- case DHCPV6_REQUEST:
- return (REQUEST);
-
- case DHCPV6_SOLICIT:
- return (SOLICIT);
-
- default:
- ;
- }
- return (UNKNOWN);
-}
-
};
};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index de9ee36..b6b0306 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -30,6 +30,23 @@
namespace isc {
namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+
/// @brief DHCPv6 server service.
///
/// This class represents DHCPv6 server. It contains all
@@ -45,6 +62,12 @@ namespace dhcp {
class Dhcpv6Srv : public boost::noncopyable {
public:
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
/// @brief Minimum length of a MAC address to be used in DUID generation.
static const size_t MIN_MAC_LEN = 6;
@@ -83,24 +106,20 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
- /// @brief Return textual type of packet received by server.
- ///
- /// Returns the name of valid packet received by the server (e.g. SOLICIT).
- /// If the packet is unknown - or if it is a valid DHCP packet but not one
- /// expected to be received by the server (such as an ADVERTISE), the string
- /// "UNKNOWN" is returned. This method is used in debug messages.
- ///
- /// As the operation of the method does not depend on any server state, it
- /// is declared static.
+protected:
+
+ /// @brief verifies if specified packet meets RFC requirements
///
- /// @param type DHCPv4 packet type
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
///
- /// @return Pointer to "const" string containing the packet name.
- /// Note that this string is statically allocated and MUST NOT
- /// be freed by the caller.
- static const char* serverReceivedPacketName(uint8_t type);
+ /// @param pkt packet to be checked
+ /// @param clientid expectation regarding client-id option
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+ RequirementLevel serverid);
-protected:
/// @brief Processes incoming SOLICIT and returns response.
///
/// Processes received SOLICIT message and verifies that its sender
@@ -186,11 +205,24 @@ protected:
/// @param question client's message (typically SOLICIT or REQUEST)
/// @param ia pointer to client's IA_NA option (client's request)
/// @return IA_NA option (server's response)
- OptionPtr handleIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
+ OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
const isc::dhcp::DuidPtr& duid,
isc::dhcp::Pkt6Ptr question,
boost::shared_ptr<Option6IA> ia);
+ /// @brief Renews specific IA_NA option
+ ///
+ /// Generates response to IA_NA. This typically includes finding a lease that
+ /// corresponds to the received address. If no such lease is found, an IA_NA
+ /// response is generated with an appropriate status code.
+ ///
+ /// @param subnet subnet the sender belongs to
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param ia IA_NA option that is being renewed
+ OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -221,14 +253,24 @@ protected:
/// @brief Assigns leases.
///
- /// TODO: This method is currently a stub. It just appends one
- /// hardcoded lease. It supports addresses (IA_NA) only. It does NOT
- /// support temporary addresses (IA_TA) nor prefixes (IA_PD).
+ /// It supports addresses (IA_NA) only. It does NOT support temporary
+ /// addresses (IA_TA) nor prefixes (IA_PD).
+ /// @todo: Extend this method once TA and PD becomes supported
///
/// @param question client's message (with requested IA_NA)
/// @param answer server's message (IA_NA options will be added here)
void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @brief Attempts to renew received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to renew
+ /// received addresses. If no such leases are found, proper status
+ /// code is added to reply message. Renewed addresses are added
+ /// as IA_NA/IAADDR to reply packet.
+ /// @param renew client's message asking for renew
+ /// @param reply server's response
+ void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index ae299e5..ced7422 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -17,6 +17,7 @@
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <boost/lexical_cast.hpp>
@@ -103,9 +104,10 @@ main(int argc, char* argv[]) {
}
// Initialize logging. If verbose, we'll use maximum verbosity.
+ // If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(DHCP6_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
LOG_INFO(dhcp6_logger, DHCP6_STARTING);
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -119,8 +121,12 @@ main(int argc, char* argv[]) {
server.establishSession();
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
- // Let's continue. It is useful to have the ability to run
+ // Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
+ // We do need to make sure logging is no longer buffered
+ // since then it would not print until dhcp6 is stopped
+ isc::log::LoggerManager log_manager;
+ log_manager.process();
}
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 9679700..de0dc28 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -19,6 +19,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
@@ -57,8 +58,10 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::processRenew;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::sanityCheck;
};
class Dhcpv6SrvTest : public ::testing::Test {
@@ -139,6 +142,33 @@ public:
return (addr);
}
+ // Checks that server rejected IA_NA, i.e. that it has no addresses and
+ // that expected status code really appears there.
+ // Status code indicates type of error encountered (in theory it can also
+ // indicate success, but servers typically don't send success status
+ // as this is the default result and it saves bandwidth)
+ void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+ uint16_t expected_status_code) {
+ // Make sure there is no address assigned.
+ EXPECT_FALSE(ia->getOption(D6O_IAADDR));
+
+ // T1, T2 should be zeroed
+ EXPECT_EQ(0, ia->getT1());
+ EXPECT_EQ(0, ia->getT2());
+
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+ EXPECT_TRUE(status);
+
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -617,7 +647,6 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
}
-
// This test verifies that incoming REQUEST can be handled properly, that a
// REPLY is generated, that the response has an address and that address
// really belongs to the configured pool.
@@ -651,6 +680,9 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
OptionPtr clientid = generateClientId();
req->addOption(clientid);
+ // server-id is mandatory in REQUEST
+ req->addOption(srv->getServerID());
+
// Pass it to the server and hope for a REPLY
Pkt6Ptr reply = srv->processRequest(req);
@@ -709,6 +741,11 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
req2->addOption(clientid2);
req3->addOption(clientid3);
+ // server-id is mandatory in REQUEST
+ req1->addOption(srv->getServerID());
+ req2->addOption(srv->getServerID());
+ req3->addOption(srv->getServerID());
+
// Pass it to the server and get an advertise
Pkt6Ptr reply1 = srv->processRequest(req1);
Pkt6Ptr reply2 = srv->processRequest(req2);
@@ -749,50 +786,207 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
}
+// This test verifies that incoming (positive) RENEW can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv6SrvTest, RenewBasic) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
-TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
- // Check all possible packet types
- for (int itype = 0; itype < 256; ++itype) {
- uint8_t type = itype;
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
- switch (type) {
- case DHCPV6_CONFIRM:
- EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
- case DHCPV6_DECLINE:
- EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
- case DHCPV6_INFORMATION_REQUEST:
- EXPECT_STREQ("INFORMATION_REQUEST",
- Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
- case DHCPV6_REBIND:
- EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Server-id is mandatory in RENEW
+ req->addOption(srv->getServerID());
- case DHCPV6_RELEASE:
- EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv->processRenew(req);
- case DHCPV6_RENEW:
- EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
- case DHCPV6_REQUEST:
- EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
- case DHCPV6_SOLICIT:
- EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
- default:
- EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
- }
- }
+ // Check that we've got the address we requested
+ checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
+
+ // Check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (invalid) RENEW can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, RenewReject) {
+
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv->getServerID());
+
+ // Case 1: No lease known to server
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv->processRenew(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ // Check that there is no lease added
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 123; // Let's use it as an indicator that the lease
+ // was NOT updated.
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv->processRenew(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ // There is a iaid mis-match, so server should respond that there is
+ // no such address to renew.
+
+ // CASE 3: Lease belongs to a client with different client-id
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv->processRenew(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ lease = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(lease);
+ // Verify that the lease was not updated.
+ EXPECT_EQ(123, lease->cltt_);
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
// This test verifies if the status code option is generated properly.
@@ -801,16 +995,27 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
// a dummy content for client-id
- uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
- OptionBuffer exp(expected, expected + sizeof(expected));
-
+ uint8_t expected[] = {
+ 0x0, 0xD, // option code = 13
+ 0x0, 0x7, // option length = 7
+ 0x0, 0x3, // status code = 3
+ 0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
+ };
+ // Create the option.
OptionPtr status = srv->createStatusCode(3, "ABCDE");
-
- EXPECT_TRUE(status->getData() == exp);
+ // Allocate an output buffer. We will store the option
+ // in wire format here.
+ OutputBuffer buf(sizeof(expected));
+ // Prepare the wire format.
+ ASSERT_NO_THROW(status->pack(buf));
+ // Check that the option buffer has valid length (option header + data).
+ ASSERT_EQ(sizeof(expected), buf.getLength());
+ // Verify the contents of the option.
+ EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
}
-// This test verifies if the selectSubnet() method works as expected.
-TEST_F(Dhcpv6SrvTest, SelectSubnet) {
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv6SrvTest, sanityCheck) {
boost::scoped_ptr<NakedDhcpv6Srv> srv;
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
@@ -818,18 +1023,63 @@ TEST_F(Dhcpv6SrvTest, SelectSubnet) {
// check that the packets originating from local addresses can be
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
- // packets originating from subnet A will select subnet A
- pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+ // client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+
+ // empty packet, no client-id, no server-id
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+
+ // This doesn't make much sense, but let's check it for completeness
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+
+ OptionPtr clientid = generateClientId();
+ pkt->addOption(clientid);
+
+ // client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+ pkt->addOption(srv->getServerID());
+
+ // both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+
+ // sane section ends here, let's do some negative tests as well
+
+ pkt->addOption(clientid);
+ pkt->addOption(clientid);
+
+ // with more than one client-id it should throw, no matter what
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+
+ pkt->delOption(D6O_CLIENTID);
+ pkt->delOption(D6O_CLIENTID);
+
+ // again we have only one client-id
+
+ // let's try different type of insanity - several server-ids
+ pkt->addOption(srv->getServerID());
+ pkt->addOption(srv->getServerID());
+
+ // with more than one server-id it should throw, no matter what
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
- // packets from a subnet that is not supported will not get
- // a subnet
- pkt->setRemoteAddr(IOAddress("3000::faf"));
- EXPECT_FALSE(srv->selectSubnet(pkt));
- /// @todo: expand this test once support for relays is implemented
}
} // end of anonymous namespace
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index d14fb0b..457f285 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -143,7 +143,7 @@ main(int argc, char* argv[]) {
// temporary initLogger() code. If verbose, we'll use maximum verbosity.
isc::log::initLogger(RESOLVER_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, true);
// Print the starting message
string cmdline = argv[0];
@@ -177,7 +177,7 @@ main(int argc, char* argv[]) {
isc::cache::ResolverCache cache;
resolver->setCache(cache);
-
+
// TODO priming query, remove root from direct
// Fake a priming query result here (TODO2 how to flag non-expiry?)
// propagation to runningquery. And check for forwarder mode?
@@ -185,21 +185,21 @@ main(int argc, char* argv[]) {
isc::dns::Name("."),
isc::dns::RRClass::IN(),
isc::dns::RRType::NS()));
- isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
+ isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
isc::dns::RRClass::IN(),
isc::dns::RRType::NS(),
isc::dns::RRTTL(8888)));
root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
isc::dns::RRClass::IN(),
"l.root-servers.net."));
- isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
isc::dns::RRClass::IN(),
isc::dns::RRType::A(),
isc::dns::RRTTL(8888)));
root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
isc::dns::RRClass::IN(),
"199.7.83.42"));
- isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
isc::dns::RRClass::IN(),
isc::dns::RRType::AAAA(),
isc::dns::RRTTL(8888)));
@@ -216,7 +216,7 @@ main(int argc, char* argv[]) {
cache.update(root_ns_rrset);
cache.update(root_a_rrset);
cache.update(root_aaaa_rrset);
-
+
DNSService dns_service(io_service, checkin, lookup, answer);
resolver->setDNSService(dns_service);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 36e028a..7123c53 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -31,7 +31,7 @@ import isc.util.process
import isc.log
from isc.log_messages.stats_messages import *
-isc.log.init("b10-stats")
+isc.log.init("b10-stats", buffer=True)
logger = isc.log.Logger("stats")
# Some constants for debug levels.
@@ -682,7 +682,7 @@ if __name__ == "__main__":
help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
- isc.log.init("b10-stats", "DEBUG", 99)
+ isc.log.init("b10-stats", "DEBUG", 99, buffer=True)
stats = Stats()
stats.start()
except OptionValueError as ove:
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index 63eb0f3..057b8ca 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -39,7 +39,7 @@ import isc.util.process
import isc.log
from isc.log_messages.stats_httpd_messages import *
-isc.log.init("b10-stats-httpd")
+isc.log.init("b10-stats-httpd", buffer=True)
logger = isc.log.Logger("stats-httpd")
# Some constants for debug levels.
@@ -609,7 +609,7 @@ if __name__ == "__main__":
help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
- isc.log.init("b10-stats-httpd", "DEBUG", 99)
+ isc.log.init("b10-stats-httpd", "DEBUG", 99, buffer=True)
stats_httpd = StatsHttpd()
stats_httpd.start()
except OptionValueError as ove:
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 231681c..eb16ab3 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -112,20 +112,18 @@ in separate zonemgr process.
<varname>master_addr</varname> (the zone master to transfer from),
<varname>master_port</varname> (defaults to 53),
<varname>use_ixfr</varname> (defaults to false), and
- <varname>tsig_key</varname> (optional TSIG key to use).
- The <varname>tsig_key</varname> is specified using a full string
- colon-delimited name:key:algorithm representation (e.g.
- <quote>foo.example.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac-sha1</quote>).
+ <varname>tsig_key</varname> (optional TSIG key name to use).
+ The <varname>tsig_key</varname> is specified using a name that
+ corresponds to one of the TSIG keys configured in the global
+ TSIG key ring (<quote>/tsig_keys/keys</quote>).
</para>
<!-- TODO: document this better -->
-<!-- TODO: the tsig_key format may change -->
<para>
(The site-wide <varname>master_addr</varname> and
<varname>master_port</varname> configurations are deprecated;
use the <varname>zones</varname> list configuration instead.)
</para>
-<!-- NOTE: also tsig_key but not mentioning since so short lived. -->
<!-- TODO: formating -->
<para>
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 4e8d6ab..b7fe056 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -26,6 +26,7 @@ from xfrin import *
import xfrin
from isc.xfrin.diff import Diff
import isc.log
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
# If we use any python library that is basically a wrapper for
# a library we use as well (like sqlite3 in our datasources),
# we must make sure we import ours first; If we have special
@@ -139,6 +140,16 @@ class MockCC(MockModuleCCSession):
if identifier == "zones/use_ixfr":
return False
+ def add_remote_config_by_name(self, name, callback):
+ pass
+
+ def get_remote_config_value(self, module, identifier):
+ if module == 'tsig_keys' and identifier == 'keys':
+ return (['example.com.key.:EvAAsfU2h7uofnmqaTCrhHunGsc='], True)
+ else:
+ raise Exception('MockCC requested for unknown config value ' +
+ + module + "/" + identifier)
+
def remove_remote_config(self, module_name):
pass
@@ -229,6 +240,7 @@ class MockXfrin(Xfrin):
def _cc_setup(self):
self._tsig_key = None
self._module_cc = MockCC()
+ init_keyring(self._module_cc)
pass
def _get_db_file(self):
@@ -2427,9 +2439,10 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(str(zone_info.master_addr), zone_config['master_addr'])
self.assertEqual(zone_info.master_port, zone_config['master_port'])
if 'tsig_key' in zone_config:
- self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
+ self.assertEqual(zone_info.tsig_key_name.to_text(),
+ Name(zone_config['tsig_key']).to_text())
else:
- self.assertIsNone(zone_info.tsig_key)
+ self.assertIsNone(zone_info.tsig_key_name)
if 'use_ixfr' in zone_config and\
zone_config.get('use_ixfr'):
self.assertTrue(zone_info.use_ixfr)
@@ -2562,7 +2575,7 @@ class TestXfrin(unittest.TestCase):
{ 'name': 'test2.example.',
'master_addr': '192.0.2.9',
'master_port': 53,
- 'tsig_key': 'badkey'
+ 'tsig_key': 'badkey..'
}
]}
self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
@@ -2581,13 +2594,14 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self._check_zones_config(config)
- def common_ixfr_setup(self, xfr_mode, use_ixfr):
+ def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None):
# This helper method explicitly sets up a zone configuration with
# use_ixfr, and invokes either retransfer or refresh.
# Shared by some of the following test cases.
config = {'zones': [
{'name': 'example.com.',
'master_addr': '192.0.2.1',
+ 'tsig_key': tsig_key_str,
'use_ixfr': use_ixfr}]}
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self.assertEqual(self.xfr.command_handler(xfr_mode,
@@ -2603,6 +2617,34 @@ class TestXfrin(unittest.TestCase):
self.common_ixfr_setup('refresh', True)
self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+ def test_command_handler_retransfer_with_tsig(self):
+ self.common_ixfr_setup('retransfer', False, 'example.com.key')
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_retransfer_with_tsig_bad_key(self):
+ # bad keys should not reach xfrin, but should they somehow,
+ # they are ignored (and result in 'key not found' + error log).
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'retransfer', False, 'bad.key')
+
+ def test_command_handler_retransfer_with_tsig_unknown_key(self):
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'retransfer', False, 'no.such.key')
+
+ def test_command_handler_refresh_with_tsig(self):
+ self.common_ixfr_setup('refresh', False, 'example.com.key')
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_refresh_with_tsig_bad_key(self):
+ # bad keys should not reach xfrin, but should they somehow,
+ # they are ignored (and result in 'key not found' + error log).
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'refresh', False, 'bad.key')
+
+ def test_command_handler_refresh_with_tsig_unknown_key(self):
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'refresh', False, 'no.such.key')
+
def test_command_handler_retransfer_ixfr_disabled(self):
# Similar to the previous case, but explicitly disabled. AXFR should
# be used.
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 8e92db6..da0f207 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -34,9 +34,10 @@ from isc.datasrc import DataSourceClient, ZoneFinder
import isc.net.parse
from isc.xfrin.diff import Diff
from isc.server_common.auth_command import auth_loadzone_command
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
from isc.log_messages.xfrin_messages import *
-isc.log.init("b10-xfrin")
+isc.log.init("b10-xfrin", buffer=True)
logger = isc.log.Logger("xfrin")
# Pending system-wide debug level definitions, the ones we
@@ -69,7 +70,10 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
AUTH_MODULE_NAME = 'Auth'
XFROUT_MODULE_NAME = 'Xfrout'
+
+# Remote module and identifiers (according to their spec files)
ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
+
REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
# Constants for debug levels.
@@ -1179,7 +1183,7 @@ class ZoneInfo:
self.set_master_port(config_data.get('master_port'))
self.set_zone_class(config_data.get('class'))
- self.set_tsig_key(config_data.get('tsig_key'))
+ self.set_tsig_key_name(config_data.get('tsig_key'))
self.set_use_ixfr(config_data.get('use_ixfr'))
def set_name(self, name_str):
@@ -1240,20 +1244,32 @@ class ZoneInfo:
errmsg = "invalid zone class: " + zone_class_str
raise XfrinZoneInfoException(errmsg)
- def set_tsig_key(self, tsig_key_str):
- """Set the tsig_key for this zone, given a TSIG key string
- representation. If tsig_key_str is None, no TSIG key will
- be set. Raises XfrinZoneInfoException if tsig_key_str cannot
- be parsed."""
+ def set_tsig_key_name(self, tsig_key_str):
+ """Set the name of the tsig_key for this zone. If tsig_key_str
+ is None, no TSIG key will be used. This name is used to
+ find the TSIG key to use for transfers in the global TSIG
+ key ring.
+ Raises XfrinZoneInfoException if tsig_key_str is not a valid
+ (dns) name."""
if tsig_key_str is None:
- self.tsig_key = None
+ self.tsig_key_name = None
else:
+ # can throw a number of exceptions but it is just one
+ # call, so Exception should be OK here
try:
- self.tsig_key = TSIGKey(tsig_key_str)
- except InvalidParameter as ipe:
- logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str)
- errmsg = "bad TSIG key string: " + tsig_key_str
- raise XfrinZoneInfoException(errmsg)
+ self.tsig_key_name = Name(tsig_key_str)
+ except Exception as exc:
+ raise XfrinZoneInfoException("Bad TSIG key name: " + str(exc))
+
+ def get_tsig_key(self):
+ if self.tsig_key_name is None:
+ return None
+ result, key = get_keyring().find(self.tsig_key_name)
+ if result != isc.dns.TSIGKeyRing.SUCCESS:
+ raise XfrinZoneInfoException("TSIG key not found in keyring: " +
+ self.tsig_key_name.to_text())
+ else:
+ return key
def set_use_ixfr(self, use_ixfr):
"""Set use_ixfr. If set to True, it will use
@@ -1308,6 +1324,7 @@ class Xfrin:
self.config_handler(config_data)
self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
self._auth_config_handler)
+ init_keyring(self._module_cc)
def _cc_check_command(self):
'''This is a straightforward wrapper for cc.check_command,
@@ -1468,7 +1485,7 @@ class Xfrin:
rrclass,
self._get_db_file(),
master_addr,
- zone_info.tsig_key, request_type,
+ zone_info.get_tsig_key(), request_type,
True)
answer = create_answer(ret[0], ret[1])
else:
@@ -1491,7 +1508,7 @@ class Xfrin:
tsig_key = None
request_type = RRType.AXFR()
if zone_info:
- tsig_key = zone_info.tsig_key
+ tsig_key = zone_info.get_tsig_key()
if zone_info.use_ixfr:
request_type = RRType.IXFR()
db_file = args.get('db_file') or self._get_db_file()
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 554a195..837cafa 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -43,7 +43,7 @@ The master port as read from the configuration is not a valid port number.
% XFRIN_BAD_TSIG_KEY_STRING bad TSIG key string: %1
The TSIG key string as read from the configuration does not represent
-a valid TSIG key.
+a valid TSIG key. The key is ignored.
% XFRIN_BAD_ZONE_CLASS Invalid zone class: %1
The zone class as read from the configuration is not a valid DNS class.
@@ -160,6 +160,13 @@ run time: Time (in seconds) the complete axfr took
bytes/second: Transfer speed
+% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
+An attempt to start a transfer with TSIG was made, but the configured TSIG
+key name was not found in the TSIG key ring (configuration option
+tsig_keys/keys). The transfer is aborted. The key name that could not be
+found is shown in the log message. Check the configuration and the
+TSIG key ring.
+
% XFRIN_UNKNOWN_ERROR unknown error: %1
An uncaught exception was raised while running the xfrin daemon. The
exception message is printed in the log message.
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 835696e..f869955 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -38,7 +38,7 @@ import isc.server_common.tsig_keyring
from isc.log_messages.xfrout_messages import *
-isc.log.init("b10-xfrout")
+isc.log.init("b10-xfrout", buffer=True)
logger = isc.log.Logger("xfrout")
# Pending system-wide debug level definitions, the ones we
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 86f89fa..0412e3f 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -42,7 +42,7 @@ from isc.log_messages.zonemgr_messages import *
from isc.notify import notify_out
# Initialize logging for called modules.
-isc.log.init("b10-zonemgr")
+isc.log.init("b10-zonemgr", buffer=True)
logger = isc.log.Logger("zonemgr")
# Pending system-wide debug level definitions, the ones we
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index 1bf9c9c..cc31419 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -61,21 +61,28 @@ nullDeleter(RdataSet* rdataset_head) {
NSEC3Data*
NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
const generic::NSEC3PARAM& rdata)
{
- return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+ return (NSEC3Data::create(mem_sgmt, zone_origin,
+ rdata.getHashalg(), rdata.getFlags(),
rdata.getIterations(), rdata.getSalt()));
}
NSEC3Data*
-NSEC3Data::create(util::MemorySegment& mem_sgmt, const generic::NSEC3& rdata) {
- return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
+ const generic::NSEC3& rdata)
+{
+ return (NSEC3Data::create(mem_sgmt, zone_origin,
+ rdata.getHashalg(), rdata.getFlags(),
rdata.getIterations(), rdata.getSalt()));
}
NSEC3Data*
-NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
- uint8_t flags, uint16_t iterations,
+NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
+ uint8_t hashalg, uint8_t flags, uint16_t iterations,
const std::vector<uint8_t>& salt)
{
// NSEC3Data allocation can throw. To avoid leaking the tree, we manage
@@ -87,6 +94,11 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
mem_sgmt, ZoneTree::create(mem_sgmt, true),
boost::bind(nullDeleter, _1));
+ ZoneTree* tree = holder.get();
+ const ZoneTree::Result result =
+ tree->insert(mem_sgmt, zone_origin, NULL);
+ assert(result == ZoneTree::SUCCESS);
+
const size_t salt_len = salt.size();
void* p = mem_sgmt.allocate(sizeof(NSEC3Data) + 1 + salt_len);
diff --git a/src/lib/datasrc/memory/zone_data.h b/src/lib/datasrc/memory/zone_data.h
index 09306d7..974ce24 100644
--- a/src/lib/datasrc/memory/zone_data.h
+++ b/src/lib/datasrc/memory/zone_data.h
@@ -90,9 +90,11 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c NSEC3Data is allocated.
+ /// \param zone_origin The zone origin.
/// \param rdata An NSEC3PARAM RDATA that specifies the NSEC3 parameters
/// to be stored.
static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
const dns::rdata::generic::NSEC3PARAM& rdata);
/// \brief Allocate and construct \c NSEC3Data from NSEC3 Rdata.
@@ -104,9 +106,11 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c NSEC3Data is allocated.
+ /// \param zone_origin The zone origin.
/// \param rdata An NSEC3 RDATA that specifies the NSEC3 parameters
/// to be stored.
static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
const dns::rdata::generic::NSEC3& rdata);
/// \brief Destruct and deallocate \c NSEC3Data.
@@ -193,8 +197,10 @@ public:
private:
// Common subroutine for the public versions of create().
- static NSEC3Data* create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
- uint8_t flags, uint16_t iterations,
+ static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
+ uint8_t hashalg, uint8_t flags,
+ uint16_t iterations,
const std::vector<uint8_t>& salt);
/// \brief The constructor.
@@ -366,9 +372,9 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c ZoneData is allocated.
- /// \param name The zone name.
+ /// \param zone_origin The zone origin.
static ZoneData* create(util::MemorySegment& mem_sgmt,
- const dns::Name& zone_name);
+ const dns::Name& zone_origin);
/// \brief Destruct and deallocate \c ZoneData.
///
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 7605644..51ec03c 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -233,7 +233,7 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
if (nsec3_data == NULL) {
- nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata);
+ nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata);
zone_data_.setNSEC3Data(nsec3_data);
zone_data_.setSigned(true);
} else {
diff --git a/src/lib/datasrc/tests/memory/testdata/2503-test.zone b/src/lib/datasrc/tests/memory/testdata/2503-test.zone
new file mode 100644
index 0000000..1609dee
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/2503-test.zone
@@ -0,0 +1,13 @@
+example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com. 3600 IN RRSIG SOA 7 2 3600 20130107110921 20121210110921 55148 example.com. 3yVTkwBE98bAcJJHKwk4WL2bKHQqfejzMdcmtteH/A2H07QwexShqQiRhNNyTRj3s1FfmMYlbd67TDZhUnWAwCdMQICj2BS0hzqiZr9iftGsAkIah6LCFx9nRPNIz2CsqXMf9D7rvzKBDUNBIliYGa5h9d7MalAlM7tZAY9YnH0=
+example.com. 3600 IN A 192.0.2.1
+example.com. 3600 IN RRSIG A 7 2 3600 20130107110921 20121210110921 55148 example.com. uCgf94psLo1wWZqAFEcy+5hM1Uh2hAwEVLyiiLESW+jz/cPeOjNFpa2PN1DIUjf2Dj40dDokM3Ev1rSjR6Z+rfHri8rUsahOEw3ZU+UrEDd5cf9MYeo4MlQIrBMAuz3AKOU3YXy7AiPEwIxtlrqrI6o1qGeX8+zKqhjGgnS0/vI=
+example.com. 3600 IN NS example.com.
+example.com. 3600 IN RRSIG NS 7 2 3600 20130107110921 20121210110921 55148 example.com. dDUDANrEdt46e7793dHxl1zOCWm88gLbr3mHYGSbIBfbtEBtISNGL1nwslejyZKnzhYM2kT8bZ3YGe5ibc9wPlGj29nUJgFwqeyIsWJhffhUCaWKYrDWod0WZLJw5uZcS2QHCuQ0+ZMm8dQoL1qswcahmh0VUI7BEKTHk9RJgzc=
+example.com. 3600 IN DNSKEY 256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b}
+example.com. 3600 IN DNSKEY 256 3 7 AwEAAe1s2ycmRzexpaUVjRZAd25ybIv7qP+Rh/tF8zIm2bMGLZl5ByxeyajZpPaGRK2xXWdY4aKBAcDELbOPQ+lLPI4RsiZBY+jY5JuzE5inkpyTtwBFjQl6o3AdBC08BmZjrpWnh9rFx/o7tk0ekU6UblUlhpqxRCDwZPvRSIVurrIr ;{id = 55148 (zsk), size = 1024b}
+example.com. 3600 IN RRSIG DNSKEY 7 2 3600 20130107110921 20121210110921 55148 example.com. FmOXL+p4GIyRA+LEWRjmoSfvWLkimw58dYrO3LS3hS0hRXP7dkhgMmuYFTOlfcfi53R9sQ9DsH6YsZwR5l0rkBE+GDq+MCfYcKXmQ7OLJZjH/3BnJgQtAlktVTfyie+kWOmwPl3l9jfim7Rmr6anXz3fYvy4S06iTsJ9z2weNJU=
+example.com. 3600 IN NSEC3PARAM 1 0 1 -
+example.com. 3600 IN RRSIG NSEC3PARAM 7 2 3600 20130107110921 20121210110921 55148 example.com. zIVekIFafb9uc20ATatQJeSV6QULdvg4VWDAWS4tnQgipPdA6VUggeBXSBaAdZu24wh5ObEdeKMChZOuiHcBYKtveQxdWzYEP0gEVvNXK+jTHpHh+siONJetWdfeCRgVma3F134ckCsS2wDSB9pzjV5lJfDmFa4muaTYZswAM8I=
+9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN NSEC3 1 0 1 - 9vq38lj9qs6s1aruer131mbtsfnvek2p A NS SOA RRSIG DNSKEY NSEC3PARAM
+9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN RRSIG NSEC3 7 3 7200 20130107110921 20121210110921 55148 example.com. q3j367t58w/NJ41I27dSFvPFQYiAFMl9hh3nwtZCaSc+KxFK+zvWP7Cm21oRDr+4oJCFkmm0kCSFGwarbFHmqJcVuorH6AKtm8Aiy3uQDvRdBZh/P+uBu4gruQSUaT7jVQLi9WdqR25nPtPC8zuxE3Yy4iRZxtZqQ/JVJFlQ/VM=
diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index d99872c..ef5f08b 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -30,4 +30,5 @@ EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone
+EXTRA_DIST += 2503-test.zone
EXTRA_DIST += 2504-test.zone
diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
index 3c28cec..1605fa2 100644
--- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
@@ -85,11 +85,16 @@ protected:
// Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
template <typename RdataType>
void
-checkNSEC3Data(MemorySegmentTest& mem_sgmt, const RdataType& expect_rdata) {
- NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, expect_rdata);
+checkNSEC3Data(MemorySegmentTest& mem_sgmt,
+ const Name& zone_name,
+ const RdataType& expect_rdata)
+{
+ NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, zone_name,
+ expect_rdata);
- // Internal tree should be created and empty.
- EXPECT_EQ(0, nsec3_data->getNSEC3Tree().getNodeCount());
+ // Internal tree should be created and must contain just the origin
+ // node.
+ EXPECT_EQ(1, nsec3_data->getNSEC3Tree().getNodeCount());
EXPECT_EQ(expect_rdata.getHashalg(), nsec3_data->hashalg);
EXPECT_EQ(expect_rdata.getFlags(), nsec3_data->flags);
@@ -117,18 +122,18 @@ checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type,
TEST_F(ZoneDataTest, createNSEC3Data) {
// Create an NSEC3Data object from various types of RDATA (of NSEC3PARAM
// and of NSEC3), check if the resulting parameters match.
- checkNSEC3Data(mem_sgmt_, param_rdata_); // one 'usual' form of params
- checkNSEC3Data(mem_sgmt_, param_rdata_nosalt_); // empty salt
- checkNSEC3Data(mem_sgmt_, param_rdata_largesalt_); // max-len salt
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_); // one 'usual' form
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_nosalt_); // empty salt
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_largesalt_); // max-len salt
// Same concepts of the tests, using NSEC3 RDATA.
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_);
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_nosalt_);
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_largesalt_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_nosalt_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_largesalt_);
}
TEST_F(ZoneDataTest, addNSEC3) {
- nsec3_data_ = NSEC3Data::create(mem_sgmt_, param_rdata_);
+ nsec3_data_ = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_);
ZoneNode* node = NULL;
nsec3_data_->insertName(mem_sgmt_, nsec3_rrset_->getName(), &node);
@@ -161,7 +166,8 @@ TEST_F(ZoneDataTest, exceptionSafetyOnCreate) {
// will fail due to bad_alloc. It shouldn't cause memory leak
// (that would be caught in TearDown()).
mem_sgmt_.setThrowCount(2);
- EXPECT_THROW(NSEC3Data::create(mem_sgmt_, param_rdata_), std::bad_alloc);
+ EXPECT_THROW(NSEC3Data::create(mem_sgmt_, zname_, param_rdata_),
+ std::bad_alloc);
// allocate() will throw on the insertion of the origin node.
mem_sgmt_.setThrowCount(2);
@@ -214,7 +220,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) {
// Set a new one. The set method should return NULL. The get method
// should return the new one.
- NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, param_rdata_);
+ NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_);
NSEC3Data* old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data);
EXPECT_EQ(static_cast<NSEC3Data*>(NULL), old_nsec3_data);
EXPECT_EQ(nsec3_data, zone_data_->getNSEC3Data());
@@ -222,7 +228,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) {
// Replace an existing one with a yet another one.
// We're responsible for destroying the old one.
- NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, nsec3_rdata_);
+ NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, zname_, nsec3_rdata_);
old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data2);
EXPECT_EQ(nsec3_data, old_nsec3_data);
EXPECT_EQ(nsec3_data2, zone_data_->getNSEC3Data());
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index 510e39f..42667da 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -1587,4 +1587,29 @@ TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
DataSourceError);
}
+// \brief testcase for #2503 (Problem in inmem NSEC3 denial of existence
+// handling)
+TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
+ // Set back the default hash calculator.
+ DefaultNSEC3HashCreator creator;
+ setNSEC3HashCreator(&creator);
+
+ shared_ptr<ZoneTableSegment> ztable_segment(
+ new ZoneTableSegmentTest(class_, mem_sgmt_));
+ InMemoryClient client(ztable_segment, class_);
+ Name name("example.com.");
+
+ client.load(name, TEST_DATA_DIR "/2503-test.zone");
+ DataSourceClient::FindResult result(client.findZone(name));
+
+ // Check for a non-existing name
+ Name search_name("nonexist.example.com.");
+ ZoneFinder::FindNSEC3Result find_result(
+ result.zone_finder->findNSEC3(search_name, true));
+ // findNSEC3() must have completed (not throw or assert). Because
+ // the find was recursive, it always must find something and return
+ // true.
+ EXPECT_TRUE(find_result.matched);
+}
+
}
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 85048b0..f41ce53 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -33,10 +33,12 @@ libb10_dhcp___la_SOURCES += option6_int_array.h
libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index a997d31..2773d09 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/option_definition.h>
+#include <dhcp/std_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -45,7 +46,7 @@ OptionDefContainer LibDHCP::v4option_defs_;
OptionDefContainer LibDHCP::v6option_defs_;
const OptionDefContainer&
-LibDHCP::getOptionDefs(Option::Universe u) {
+LibDHCP::getOptionDefs(const Option::Universe u) {
switch (u) {
case Option::V4:
initStdOptionDefs4();
@@ -60,6 +61,17 @@ LibDHCP::getOptionDefs(Option::Universe u) {
}
}
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
+ const OptionDefContainer& defs = getOptionDefs(u);
+ const OptionDefContainerTypeIndex& idx = defs.get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+ return (OptionDefinitionPtr());
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
@@ -254,52 +266,16 @@ void
LibDHCP::initStdOptionDefs6() {
v6option_defs_.clear();
- struct OptionParams {
- std::string name;
- uint16_t code;
- OptionDataType type;
- bool array;
- };
- OptionParams params[] = {
- { "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
- { "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
- { "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
- { "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
- { "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
- { "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
- { "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
- { "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
- { "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
- { "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
- };
- const int params_size = sizeof(params) / sizeof(params[0]);
-
- for (int i = 0; i < params_size; ++i) {
- OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
- params[i].code,
- params[i].type,
- params[i].array));
- switch(params[i].code) {
- case D6O_IA_NA:
- case D6O_IA_PD:
- for (int j = 0; j < 3; ++j) {
- definition->addRecordField(OPT_UINT32_TYPE);
- }
- break;
- case D6O_IAADDR:
- definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- break;
- case D6O_STATUS_CODE:
- definition->addRecordField(OPT_UINT16_TYPE);
- definition->addRecordField(OPT_STRING_TYPE);
- break;
- default:
- // The default case is intentionally left empty
- // as it does not need any processing.
- ;
+ for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
+ OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+ OPTION_DEF_PARAMS6[i].code,
+ OPTION_DEF_PARAMS6[i].type,
+ OPTION_DEF_PARAMS6[i].array));
+
+ for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
+ definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
}
+
try {
definition->validate();
} catch (const Exception& ex) {
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index cab7928..e855070 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -42,7 +42,18 @@ public:
/// @param u universe of the options (V4 or V6).
///
/// @return collection of option definitions.
- static const OptionDefContainer& getOptionDefs(Option::Universe u);
+ static const OptionDefContainer& getOptionDefs(const Option::Universe u);
+
+ /// @brief Return the first option definition matching a
+ /// particular option code.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return reference to an option definition being requested
+ /// or NULL pointer if option definition has not been found.
+ static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+ const uint16_t code);
/// @brief Factory function to create instance of option.
///
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index 4638025..2a53f0f 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -314,6 +314,10 @@ void Option::setData(const OptionBufferConstIter first,
std::copy(first, last, data_.begin());
}
+bool Option::equal(const OptionPtr& other) const {
+ return ( (getType() == other->getType()) &&
+ (getData() == other->getData()) );
+}
Option::~Option() {
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index 22ac291..3c7799f 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -219,7 +219,7 @@ public:
///
/// @return pointer to actual data (or reference to an empty vector
/// if there is no data)
- virtual const OptionBuffer& getData() { return (data_); }
+ virtual const OptionBuffer& getData() const { return (data_); }
/// Adds a sub-option.
///
@@ -303,6 +303,19 @@ public:
/// just to force that every option has virtual dtor
virtual ~Option();
+ /// @brief Checks if two options are equal
+ ///
+ /// Equality verifies option type and option content. Care should
+ /// be taken when using this method. Implementation for derived
+ /// classes should be provided when this method is expected to be
+ /// used. It is safe in general, as the first check (different types)
+ /// will detect differences between base Option and derived
+ /// objects.
+ ///
+ /// @param other the other option
+ /// @return true if both options are equal
+ virtual bool equal(const OptionPtr& other) const;
+
protected:
/// Builds raw (over-wire) buffer of this option, including all
/// defined suboptions. Version for building DHCPv4 options.
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
index 54624d0..32f3667 100644
--- a/src/lib/dhcp/option6_ia.h
+++ b/src/lib/dhcp/option6_ia.h
@@ -68,12 +68,17 @@ public:
/// Sets T1 timer.
///
/// @param t1 t1 value to be set
- void setT1(uint32_t t1) { t1_=t1; }
+ void setT1(uint32_t t1) { t1_ = t1; }
/// Sets T2 timer.
///
/// @param t2 t2 value to be set
- void setT2(uint32_t t2) { t2_=t2; }
+ void setT2(uint32_t t2) { t2_ = t2; }
+
+ /// Sets Identity Association Identifier.
+ ///
+ /// @param iaid IAID value to be set
+ void setIAID(uint32_t iaid) { iaid_ = iaid; }
/// Returns IA identifier.
///
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 8b3ef11..0c3cb81 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -21,11 +21,28 @@ namespace isc {
namespace dhcp {
OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u)
+ : Option(u, def.getCode(), OptionBuffer()),
+ definition_(def) {
+ createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u,
const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
- createBuffers();
+ // It is possible that no data is provided if an option
+ // is being created on a server side. In such case a bunch
+ // of buffers with default values is first created and then
+ // the values are replaced using writeXXX functions. Thus
+ // we need to detect that no data has been specified and
+ // take a different code path.
+ if (!data_.empty()) {
+ createBuffers(data_);
+ } else {
+ createBuffers();
+ }
}
OptionCustom::OptionCustom(const OptionDefinition& def,
@@ -34,25 +51,156 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers();
+ createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+ checkArrayType();
+
+ if ((address.getFamily() == AF_INET &&
+ definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+ (address.getFamily() == AF_INET6 &&
+ definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6")
+ << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeBool(value, buf);
+ buffers_.push_back(buf);
}
void
OptionCustom::checkIndex(const uint32_t index) const {
if (index >= buffers_.size()) {
isc_throw(isc::OutOfRange, "specified data field index " << index
- << " is out of rangex.");
+ << " is out of range.");
+ }
+}
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+ // Check that the requested return type is a supported integer.
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+ " is not a supported integer type.");
+ }
+
+ // Get the option definition type.
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ definition_.getRecordFields();
+ // When we initialized buffers we have already checked that
+ // the number of these buffers is equal to number of option
+ // fields in the record so the condition below should be met.
+ assert(index < record_fields.size());
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ }
+
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition for field"
+ " index " << index);
}
}
void
OptionCustom::createBuffers() {
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+
+ OptionDataType data_type = definition_.getType();
+ // This function is called when an empty data buffer has been
+ // passed to the constructor. In such cases values for particular
+ // data fields will be set using modifier functions but for now
+ // we need to initialize a set of buffers that are specified
+ // for an option by its definition. Since there is no data yet,
+ // we are going to fill these buffers with default values.
+ if (data_type == OPT_RECORD_TYPE) {
+ // For record types we need to iterate over all data fields
+ // specified in option definition and create corresponding
+ // buffers for each of them.
+ const OptionDefinition::RecordFieldsCollection fields =
+ definition_.getRecordFields();
+
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ OptionBuffer buf;
+
+ // For data types that have a fixed size we can use the
+ // utility function to get the buffer's size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable data sizes the utility function returns zero.
+ // It is ok for string values because the default string
+ // is 'empty'. However for FQDN the empty value is not valid
+ // so we initialize it to '.'.
+ if (data_size == 0 &&
+ *field == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // At this point we can resize the buffer. Note that
+ // for string values we are setting the empty buffer
+ // here.
+ buf.resize(data_size);
+ }
+ // We have the buffer with default value prepared so we
+ // add it to the set of buffers.
+ buffers.push_back(buf);
+ }
+ } else if (!definition_.getArrayType() &&
+ data_type != OPT_EMPTY_TYPE) {
+ // For either 'empty' options we don't have to create any buffers
+ // for obvious reason. For arrays we also don't create any buffers
+ // yet because the set of fields that belong to the array is open
+ // ended so we can't allocate required buffers until we know how
+ // many of them are needed.
+ // For non-arrays we have a single value being held by the option
+ // so we have to allocate exactly one buffer.
+ OptionBuffer buf;
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ if (data_size == 0 &&
+ data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // Note that if our option holds a string value then
+ // we are making empty buffer here.
+ buf.resize(data_size);
+ }
+ // Add a buffer that we have created and leave.
+ buffers.push_back(buf);
+ }
+ // The 'swap' is used here because we want to make sure that we
+ // don't touch buffers_ until we successfully allocate all
+ // buffers to be stored there.
+ std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// Check that the option definition is correct as we are going
// to use it to split the data_ buffer into set of sub buffers.
definition_.validate();
std::vector<OptionBuffer> buffers;
- OptionBuffer::iterator data = data_.begin();
+ OptionBuffer::const_iterator data = data_buf.begin();
OptionDataType data_type = definition_.getType();
if (data_type == OPT_RECORD_TYPE) {
@@ -68,17 +216,31 @@ OptionCustom::createBuffers() {
// For fixed-size data type such as boolean, integer, even
// IP address we can use the utility function to get the required
// buffer size.
- int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
- // For variable size types (such as string) the function above
- // will return 0 so we need to do a runtime check. Since variable
- // length data fields may be laid only at the end of an option we
- // consume the rest of this option. Note that validate() function
- // in OptionDefinition object should have checked whether the
- // data fields layout is correct (that the variable string fields
- // are laid at the end).
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable size types (e.g. string) the function above will
+ // return 0 so we need to do a runtime check of the length.
if (data_size == 0) {
- data_size = std::distance(data, data_.end());
+ // FQDN is a special data type as it stores variable length data
+ // but the data length is encoded in the buffer. The easiest way
+ // to obtain the length of the data is to read the FQDN. The
+ // utility function will return the size of the buffer on success.
+ if (*field == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ // In other case we are dealing with string or binary value
+ // which size can't be determined. Thus we consume the
+ // remaining part of the buffer for it. Note that variable
+ // size data can be laid at the end of the option only and
+ // that the validate() function in OptionDefinition object
+ // should have checked wheter it is a case for this option.
+ data_size = std::distance(data, data_buf.end());
+ }
if (data_size == 0) {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
@@ -90,7 +252,7 @@ OptionCustom::createBuffers() {
} else {
// Our data field requires that there is a certain chunk of
// data left in the buffer. If not, option is truncated.
- if (std::distance(data, data_.end()) < data_size) {
+ if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
}
@@ -105,39 +267,64 @@ OptionCustom::createBuffers() {
// data fields of the same type. The type of those fields
// is held in the data_type variable so let's use it to determine
// a size of buffers.
- int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
// The check below will fail if the input buffer is too short
// for the data size being held by this option.
// Note that data_size returned by getDataTypeLen may be zero
// if variable length data is being held by the option but
// this will not cause this check to throw exception.
- if (std::distance(data, data_.end()) < data_size) {
+ if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
// For an array of values we are taking different path because
// we have to handle multiple buffers.
if (definition_.getArrayType()) {
- // We don't perform other checks for data types that can't be
- // used together with array indicator such as strings, empty field
- // etc. This is because OptionDefinition::validate function should
- // have checked this already. Thus data_size must be greater than
- // zero.
- assert(data_size > 0);
- // Get equal chunks of data and store as collection of buffers.
- // Truncate any remaining part which length is not divisible by
- // data_size. Note that it is ok to truncate the data if and only
- // if the data buffer is long enough to keep at least one value.
- // This has been checked above already.
- do {
+ while (data != data_buf.end()) {
+ // FQDN is a special case because it is of a variable length.
+ // The actual length for a particular FQDN is encoded within
+ // a buffer so we have to actually read the FQDN from a buffer
+ // to get it.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ }
+ // We don't perform other checks for data types that can't be
+ // used together with array indicator such as strings, empty field
+ // etc. This is because OptionDefinition::validate function should
+ // have checked this already. Thus data_size must be greater than
+ // zero.
+ assert(data_size > 0);
+ // Get chunks of data and store as a collection of buffers.
+ // Truncate any remaining part which length is not divisible by
+ // data_size. Note that it is ok to truncate the data if and only
+ // if the data buffer is long enough to keep at least one value.
+ // This has been checked above already.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
- } while (std::distance(data, data_.end()) >= data_size);
+ }
} else {
// For non-arrays the data_size can be zero because
// getDataTypeLen returns zero for variable size data types
// such as strings. Simply take whole buffer.
if (data_size == 0) {
- data_size = std::distance(data, data_.end());
+ // For FQDN we get the size by actually reading the FQDN.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 bytes larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ data_size = std::distance(data, data_buf.end());
+ }
}
if (data_size > 0) {
buffers.push_back(OptionBuffer(data, data + data_size));
@@ -185,6 +372,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
case OPT_IPV6_ADDRESS_TYPE:
text << readAddress(index).toText();
break;
+ case OPT_FQDN_TYPE:
+ text << readFqdn(index);
+ break;
case OPT_STRING_TYPE:
text << readString(index);
break;
@@ -252,8 +442,31 @@ OptionCustom::readAddress(const uint32_t index) const {
return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
} else {
isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
- << " IP address. Invalid buffer length " << buffers_[index].size());
+ << " IP address. Invalid buffer length "
+ << buffers_[index].size() << ".");
+ }
+}
+
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index) {
+ using namespace isc::asiolink;
+
+ checkIndex(index);
+
+ if ((address.getFamily() == AF_INET &&
+ buffers_[index].size() != V4ADDRESS_LEN) ||
+ (address.getFamily() == AF_INET6 &&
+ buffers_[index].size() != V6ADDRESS_LEN)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+ << " address.");
}
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ std::swap(buf, buffers_[index]);
}
const OptionBuffer&
@@ -262,12 +475,50 @@ OptionCustom::readBinary(const uint32_t index) const {
return (buffers_[index]);
}
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+ const uint32_t index) {
+ checkIndex(index);
+ buffers_[index] = buf;
+}
+
bool
OptionCustom::readBoolean(const uint32_t index) const {
checkIndex(index);
return (OptionDataTypeUtil::readBool(buffers_[index]));
}
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+ checkIndex(index);
+
+ // Create a temporay buffer where the FQDN will be written.
+ OptionBuffer buf;
+ // Try to write to the temporary buffer rather than to the
+ // buffers_ member directly guarantees that we don't modify
+ // (clear) buffers_ until we are sure that the provided FQDN
+ // is valid.
+ OptionDataTypeUtil::writeFqdn(fqdn, buf);
+ // If we got to this point it means that the FQDN is valid.
+ // We can move the contents of the teporary buffer to the
+ // target buffer.
+ std::swap(buffers_[index], buf);
+}
+
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
@@ -275,11 +526,26 @@ OptionCustom::readString(const uint32_t index) const {
}
void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+ checkIndex(index);
+
+ // Let's clear a buffer as we want to replace the value of the
+ // whole buffer. If we fail to clear the buffer the data will
+ // be appended.
+ buffers_[index].clear();
+ // If the text value is empty we can leave because the buffer
+ // is already empty.
+ if (!text.empty()) {
+ OptionDataTypeUtil::writeString(text, buffers_[index]);
+ }
+}
+
+void
OptionCustom::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
data_ = OptionBuffer(begin, end);
// Chop the buffer stored in data_ into set of sub buffers.
- createBuffers();
+ createBuffers(data_);
}
uint16_t
@@ -311,7 +577,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers();
+ createBuffers(data_);
}
std::string OptionCustom::toText(int indent) {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index ad248c5..0ee4688 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -40,6 +40,17 @@ public:
/// @brief Constructor, used for options to be sent.
///
+ /// This constructor creates an instance of an option with default
+ /// data set for all data fields. The option buffers are allocated
+ /// according to data size being stored in particular data fields.
+ /// For variable size data empty buffers are created.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6)
+ OptionCustom(const OptionDefinition& def, Universe u);
+
+ /// @brief Constructor, used for options to be sent.
+ ///
/// This constructor creates an instance of an option from the whole
/// supplied buffer. This constructor is mainly used to create an
/// instances of options to be stored in outgoing DHCP packets.
@@ -76,6 +87,37 @@ public:
OptionCustom(const OptionDefinition& def, Universe u,
OptionBufferConstIter first, OptionBufferConstIter last);
+ /// @brief Create new buffer and set its value as an IP address.
+ ///
+ /// @param address IPv4 or IPv6 address to be written to
+ /// a buffer being created.
+ void addArrayDataField(const asiolink::IOAddress& address);
+
+ /// @brief Create new buffer and store boolean value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ void addArrayDataField(const bool value);
+
+ /// @brief Create new buffer and store integer value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ /// @tparam T integer type of the value being stored.
+ template<typename T>
+ void addArrayDataField(const T value) {
+ checkArrayType();
+
+ OptionDataType data_type = definition_.getType();
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ buffers_.push_back(buf);
+ }
+
/// @brief Return a number of the data fields.
///
/// @return number of data fields held by the option.
@@ -87,7 +129,17 @@ public:
///
/// @return IP address read from a buffer.
/// @throw isc::OutOfRange if index is out of range.
- asiolink::IOAddress readAddress(const uint32_t index) const;
+ asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+ /// @brief Write an IP address into a buffer.
+ ///
+ /// @param address IP address being written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+ void writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index = 0);
/// @brief Read a buffer as binary data.
///
@@ -95,7 +147,13 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read buffer holding binary data.
- const OptionBuffer& readBinary(const uint32_t index) const;
+ const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+ /// @brief Write binary data into a buffer.
+ ///
+ /// @param buf buffer holding binary data to be written.
+ /// @param index buffer index.
+ void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
/// @brief Read a buffer as boolean value.
///
@@ -103,7 +161,33 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read boolean value.
- bool readBoolean(const uint32_t index) const;
+ bool readBoolean(const uint32_t index = 0) const;
+
+ /// @brief Write a boolean value into a buffer.
+ ///
+ /// @param value boolean value to be written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeBoolean(const bool value, const uint32_t index = 0);
+
+ /// @brief Read a buffer as FQDN.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if buffer index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+ /// does not hold a valid FQDN.
+ /// @return string representation if FQDN.
+ std::string readFqdn(const uint32_t index = 0) const;
+
+ /// @brief Write an FQDN into a buffer.
+ ///
+ /// @param fqdn text representation of FQDN.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
/// @brief Read a buffer as integer value.
///
@@ -111,38 +195,15 @@ public:
/// @tparam integer type of a value being returned.
///
/// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
/// @return read integer value.
template<typename T>
- T readInteger(const uint32_t index) const {
+ T readInteger(const uint32_t index = 0) const {
+ // Check that the index is not out of range.
checkIndex(index);
-
- // Check that the requested return type is a supported integer.
- if (!OptionDataTypeTraits<T>::integer_type) {
- isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
- " by readInteger is not supported integer type");
- }
-
- // Get the option definition type.
- OptionDataType data_type = definition_.getType();
- if (data_type == OPT_RECORD_TYPE) {
- const OptionDefinition::RecordFieldsCollection& record_fields =
- definition_.getRecordFields();
- // When we initialized buffers we have already checked that
- // the number of these buffers is equal to number of option
- // fields in the record so the condition below should be met.
- assert(index < record_fields.size());
- // Get the data type to be returned.
- data_type = record_fields[index];
- }
-
- // Requested data type must match the data type in a record.
- if (OptionDataTypeTraits<T>::type != data_type) {
- isc_throw(isc::dhcp::InvalidDataType,
- "unable to read option field with index " << index
- << " as integer value. The field's data type"
- << data_type << " does not match the integer type"
- << "returned by the readInteger function.");
- }
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
// When we created the buffer we have checked that it has a
// valid size so this condition here should be always fulfiled.
assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
@@ -150,13 +211,43 @@ public:
return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
}
+ /// @brief Write an integer value into a buffer.
+ ///
+ /// @param value integer value to be written.
+ /// @param index buffer index.
+ /// @tparam T integer type of a value being written.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ template<typename T>
+ void writeInteger(const T value, const uint32_t index = 0) {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // Get some temporary buffer.
+ OptionBuffer buf;
+ // Try to write to the buffer.
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ // If successful, replace the old buffer with new one.
+ std::swap(buffers_[index], buf);
+ }
+
/// @brief Read a buffer as string value.
///
/// @param index buffer index.
///
/// @return string value read from buffer.
/// @throw isc::OutOfRange if index is out of range.
- std::string readString(const uint32_t index) const;
+ std::string readString(const uint32_t index = 0) const;
+
+ /// @brief Write a string value into a buffer.
+ ///
+ /// @param text the string value to be written.
+ /// @param index buffer index.
+ void writeString(const std::string& text,
+ const uint32_t index = 0);
/// @brief Parses received buffer.
///
@@ -201,6 +292,33 @@ protected:
private:
+ /// @brief Verify that the option comprises an array of values.
+ ///
+ /// This helper function is used by createArrayEntry functions
+ /// and throws an exception if the particular option is not
+ /// an array.
+ ///
+ /// @throw isc::InvalidOperation if option is not an array.
+ inline void checkArrayType() const {
+ if (!definition_.getArrayType()) {
+ isc_throw(InvalidOperation, "failed to add new array entry to an"
+ << " option. The option is not an array.");
+ }
+ }
+
+ /// @brief Verify that the integer type is consistent with option
+ /// field type.
+ ///
+ /// This convenience function checks that the data type specified as T
+ /// is consistent with a type of a data field identified by index.
+ ///
+ /// @param index data field index.
+ /// @tparam data type to be validated.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+ template<typename T>
+ void checkDataType(const uint32_t index) const;
+
/// @brief Check if data field index is valid.
///
/// @param index Data field index to check.
@@ -208,9 +326,14 @@ private:
/// @throw isc::OutOfRange if index is out of range.
void checkIndex(const uint32_t index) const;
- /// @brief Create collection of buffers representing data field values.
+ /// @brief Create a collection of non initialized buffers.
void createBuffers();
+ /// @brief Create collection of buffers representing data field values.
+ ///
+ /// @param data_buf a buffer to be parsed.
+ void createBuffers(const OptionBuffer& data_buf);
+
/// @brief Return a text representation of a data field.
///
/// @param data_type data type of a field.
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index de09bc4..0c512d7 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -13,6 +13,8 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
#include <util/encode/hex.h>
namespace isc {
@@ -207,6 +209,43 @@ OptionDataTypeUtil::writeBool(const bool value,
}
std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+ // If buffer is empty emit an error.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+ << " The buffer is empty.");
+ }
+ // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+ isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+ try {
+ // Try to create an object from the buffer. If exception is thrown
+ // it means that the buffer doesn't hold a valid domain name (invalid
+ // syntax).
+ isc::dns::Name name(in_buf);
+ return (name.toText());
+ } catch (const isc::Exception& ex) {
+ // Unable to convert the data in the buffer into FQDN.
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf) {
+ try {
+ isc::dns::Name name(fqdn);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ buf.insert(buf.end(), data, data + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+std::string
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
std::string value;
if (!buf.empty()) {
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index afc6d93..ff5789d 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -225,11 +225,13 @@ public:
/// @return data type size or zero for variable length types.
static int getDataTypeLen(const OptionDataType data_type);
- /// @brief Read IPv4 or IPv6 addres from a buffer.
+ /// @brief Read IPv4 or IPv6 address from a buffer.
///
/// @param buf input buffer.
/// @param family address family: AF_INET or AF_INET6.
///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
/// @return address being read.
static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
const short family);
@@ -252,6 +254,9 @@ public:
/// @brief Read boolean value from a buffer.
///
/// @param buf input buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated or the value is invalid (neither 1 nor 0).
/// @return boolean value read from a buffer.
static bool readBool(const std::vector<uint8_t>& buf);
@@ -268,6 +273,9 @@ public:
///
/// @param buf input buffer.
/// @tparam integer type of the returned value.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+ /// is truncated.
/// @return integer value being read.
template<typename T>
static T readInt(const std::vector<uint8_t>& buf) {
@@ -276,7 +284,12 @@ public:
" by readInteger is unsupported integer type");
}
- assert(buf.size() == OptionDataTypeTraits<T>::len);
+ if (buf.size() < OptionDataTypeTraits<T>::len) {
+ isc_throw(isc::dhcp::BadDataTypeCast,
+ "failed to read an integer value from a buffer"
+ << " - buffer is truncated.");
+ }
+
T value;
switch (OptionDataTypeTraits<T>::len) {
case 1:
@@ -332,6 +345,33 @@ public:
}
}
+ /// @brief Read FQDN from a buffer as a string value.
+ ///
+ /// The format of an FQDN within a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param buf input buffer holding a FQDN.
+ ///
+ /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+ /// invalid (e.g. empty, contains invalid characters, truncated).
+ /// @return fully qualified domain name in a text form.
+ static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+ /// @brief Append FQDN into a buffer.
+ ///
+ /// This method appends the Fully Qualified Domain Name (FQDN)
+ /// represented as string value into a buffer. The format of
+ /// the FQDN being stored into a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param fqdn fully qualified domain name to be written.
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+ /// is invalid.
+ static void writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf);
+
/// @brief Read string value from a buffer.
///
/// @param buf input buffer.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index f1e89e2..2248bd7 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -19,6 +19,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
#include <util/encode/hex.h>
@@ -78,53 +79,81 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
validate();
-
+
try {
- if (type_ == OPT_BINARY_TYPE) {
+ switch(type_) {
+ case OPT_EMPTY_TYPE:
+ return (factoryEmpty(u, type));
+
+ case OPT_BINARY_TYPE:
return (factoryGeneric(u, type, begin, end));
- } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList6(type, begin, end));
+ case OPT_UINT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<uint8_t>(u, type, begin, end));
- } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList4(type, begin, end));
+ case OPT_INT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<int8_t>(u, type, begin, end));
- } else if (type_ == OPT_EMPTY_TYPE) {
- return (factoryEmpty(u, type));
+ case OPT_UINT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<uint16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IA_NA &&
- haveIA6Format()) {
- return (factoryIA6(type, begin, end));
+ case OPT_INT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<int16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IAADDR &&
- haveIAAddr6Format()) {
- return (factoryIAAddr6(type, begin, end));
+ case OPT_UINT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<uint32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT8_TYPE) {
- if (array_type_) {
- return (factoryGeneric(u, type, begin, end));
- } else {
- return (factoryInteger<uint8_t>(u, type, begin, end));
- }
+ case OPT_INT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<int32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT16_TYPE) {
+ case OPT_IPV4_ADDRESS_TYPE:
+ // If definition specifies that an option is an array
+ // of IPv4 addresses we return an instance of specialized
+ // class (OptionAddrLst4). For non-array types there is no
+ // specialized class yet implemented so we drop through
+ // to return an instance of OptionCustom.
if (array_type_) {
- return (factoryIntegerArray<uint16_t>(type, begin, end));
- } else {
- return (factoryInteger<uint16_t>(u, type, begin, end));
+ return (factoryAddrList4(type, begin, end));
}
+ break;
- } else if (type_ == OPT_UINT32_TYPE) {
+ case OPT_IPV6_ADDRESS_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
if (array_type_) {
- return (factoryIntegerArray<uint32_t>(type, begin, end));
- } else {
- return (factoryInteger<uint32_t>(u, type, begin, end));
+ return (factoryAddrList6(type, begin, end));
+ }
+ break;
+
+ default:
+ if (u == Option::V6) {
+ if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
+ haveIA6Format()) {
+ // Return Option6IA instance for IA_PD and IA_NA option
+ // types only. We don't want to return Option6IA for other
+ // options that comprise 3 UINT32 data fields because
+ // Option6IA accessors' and modifiers' names are derived
+ // from the IA_NA and IA_PD options' field names: IAID,
+ // T1, T2. Using functions such as getIAID, getT1 etc. for
+ // options other than IA_NA and IA_PD would be bad practice
+ // and cause confusion.
+ return (factoryIA6(type, begin, end));
+
+ } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
+ // Rerurn Option6IAAddr option instance for the IAADDR
+ // option only for the same reasons as described in
+ // for IA_NA and IA_PD above.
+ return (factoryIAAddr6(type, begin, end));
+ }
}
-
}
- return (factoryGeneric(u, type, begin, end));
+ return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 3d48ef2..ca40428 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -246,7 +246,7 @@ public:
/// @throw MalformedOptionDefinition if option definition is invalid.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
- const OptionBuffer& buf) const;
+ const OptionBuffer& buf = OptionBuffer()) const;
/// @brief Option factory.
///
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index efeaf62..5139458 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -269,7 +269,7 @@ public:
///
/// @param hType hardware type (will be sent in htype field)
/// @param hlen hardware length (will be sent in hlen field)
- /// @param macAddr pointer to hardware address
+ /// @param mac_addr pointer to hardware address
void setHWAddr(uint8_t hType, uint8_t hlen,
const std::vector<uint8_t>& mac_addr);
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 12597c3..2c97b07 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -81,13 +81,6 @@ Pkt6::pack() {
bool
Pkt6::packUDP() {
-
- // TODO: Once OutputBuffer is used here, some thing like this
- // will be used. Yikes! That's ugly.
- // bufferOut_.writeData(ciaddr_.getAddress().to_v6().to_bytes().data(), 16);
- // It is better to implement a method in IOAddress that extracts
- // vector<uint8_t>
-
try {
// DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
bufferOut_.writeUint8(msg_type_);
@@ -172,17 +165,30 @@ Pkt6::toText() {
return tmp.str();
}
-boost::shared_ptr<isc::dhcp::Option>
+OptionPtr
Pkt6::getOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
if (x!=options_.end()) {
return (*x).second;
}
- return boost::shared_ptr<isc::dhcp::Option>(); // NULL
+ return OptionPtr(); // NULL
+}
+
+isc::dhcp::Option::OptionCollection
+Pkt6::getOptions(uint16_t opt_type) {
+ isc::dhcp::Option::OptionCollection found;
+
+ for (Option::OptionCollection::const_iterator x = options_.begin();
+ x != options_.end(); ++x) {
+ if (x->first == opt_type) {
+ found.insert(make_pair(opt_type, x->second));
+ }
+ }
+ return (found);
}
void
-Pkt6::addOption(boost::shared_ptr<Option> opt) {
+Pkt6::addOption(const OptionPtr& opt) {
options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
}
@@ -205,6 +211,52 @@ Pkt6::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
}
+const char*
+Pkt6::getName(uint8_t type) {
+ static const char* CONFIRM = "CONFIRM";
+ static const char* DECLINE = "DECLINE";
+ static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+ static const char* REBIND = "REBIND";
+ static const char* RELEASE = "RELEASE";
+ static const char* RENEW = "RENEW";
+ static const char* REQUEST = "REQUEST";
+ static const char* SOLICIT = "SOLICIT";
+ static const char* UNKNOWN = "UNKNOWN";
+
+ switch (type) {
+ case DHCPV6_CONFIRM:
+ return (CONFIRM);
+
+ case DHCPV6_DECLINE:
+ return (DECLINE);
+
+ case DHCPV6_INFORMATION_REQUEST:
+ return (INFORMATION_REQUEST);
+
+ case DHCPV6_REBIND:
+ return (REBIND);
+
+ case DHCPV6_RELEASE:
+ return (RELEASE);
+
+ case DHCPV6_RENEW:
+ return (RENEW);
+
+ case DHCPV6_REQUEST:
+ return (REQUEST);
+
+ case DHCPV6_SOLICIT:
+ return (SOLICIT);
+
+ default:
+ ;
+ }
+ return (UNKNOWN);
+}
+
+const char* Pkt6::getName() const {
+ return (getName(getType()));
+}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index ac5e787..6ffea2b 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -147,19 +147,28 @@ public:
/// Adds an option to this packet.
///
/// @param opt option to be added.
- void addOption(OptionPtr opt);
+ void addOption(const OptionPtr& opt);
/// @brief Returns the first option of specified type.
///
/// Returns the first option of specified type. Note that in DHCPv6 several
/// instances of the same option are allowed (and frequently used).
- /// See getOptions().
+ /// Also see \ref getOptions().
///
/// @param type option type we are looking for
///
/// @return pointer to found option (or NULL)
OptionPtr getOption(uint16_t type);
+ /// @brief Returns all instances of specified type.
+ ///
+ /// Returns all instances of options of the specified type. DHCPv6 protocol
+ /// allows (and uses frequently) multiple instances.
+ ///
+ /// @param type option type we are looking for
+ /// @return instance of option collection with requested options
+ isc::dhcp::Option::OptionCollection getOptions(uint16_t type);
+
/// Attempts to delete first suboption of requested type
///
/// @param type Type of option to be deleted.
@@ -250,8 +259,6 @@ public:
/// @return interface name
void setIface(const std::string& iface ) { iface_ = iface; };
- /// TODO Need to implement getOptions() as well
-
/// collection of options present in this message
///
/// @warning This protected member is accessed by derived
@@ -270,6 +277,34 @@ public:
/// @throw isc::Unexpected if timestamp update failed
void updateTimestamp();
+ /// @brief Return textual type of packet.
+ ///
+ /// Returns the name of valid packet received by the server (e.g. SOLICIT).
+ /// If the packet is unknown - or if it is a valid DHCP packet but not one
+ /// expected to be received by the server (such as an ADVERTISE), the string
+ /// "UNKNOWN" is returned. This method is used in debug messages.
+ ///
+ /// As the operation of the method does not depend on any server state, it
+ /// is declared static. There is also non-static getName() method that
+ /// works on Pkt6 objects.
+ ///
+ /// @param type DHCPv6 packet type
+ ///
+ /// @return Pointer to "const" string containing the packet name.
+ /// Note that this string is statically allocated and MUST NOT
+ /// be freed by the caller.
+ static const char* getName(uint8_t type);
+
+ /// @brief returns textual representation of packet type.
+ ///
+ /// This method requires an object. There is also static version, which
+ /// requires one parameter (type).
+ ///
+ /// @return Pointer to "const" string containing packet name.
+ /// Note that this string is statically allocated and MUST NOT
+ /// be freed by the caller.
+ const char* getName() const;
+
protected:
/// Builds on wire packet for TCP transmission.
///
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
new file mode 100644
index 0000000..d66dcf8
--- /dev/null
+++ b/src/lib/dhcp/std_option_defs.h
@@ -0,0 +1,184 @@
+// 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 STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+/// @param types data types of fields that belong to the record.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, types...) static OptionDataType name[] = { types }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+#ifndef NO_RECORD_DEF
+#define NO_RECORD_DEF 0, 0
+#endif
+
+using namespace isc::dhcp;
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ OptionDataType type; // data type
+ bool array; // is array
+ OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+};
+
+// client-fqdn
+RECORD_DECL(clientFqdnRecords, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(geoconfCivicRecords, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+ OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(iaaddrRecords, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(ianaRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(iapdRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(iaPrefixRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(lqQueryRecords, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(lqRelayData, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(remoteIdRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(statusCodeRecords, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(vendorClassRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(vendorOptsRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+/// Standard DHCPv6 option definitions.
+///
+/// @warning in this array, the initializers are provided for all
+/// OptionDefParams struct's members despite initializers for
+/// 'records' and 'record_size' could be ommited for entries for
+/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers
+/// are ommitted the corresponding values should default to 0.
+/// This however does not work on Solaris (GCC) which issues a
+/// warning about lack of initializers for some struct members
+/// causing build to fail.
+static const OptionDefParams OPTION_DEF_PARAMS6[] = {
+ { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(ianaRecords) },
+ { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(iaaddrRecords) },
+ { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ // Unfortunatelly the AUTH option contains a 64-bit data field
+ // called 'replay-detection' that can't be added as a record
+ // field to a custom option. Also, there is no dedicated
+ // option class to handle it so we simply return binary
+ // option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(statusCodeRecords) },
+ { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+ { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(vendorClassRecords) },
+ { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(vendorOptsRecords) },
+ { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false,
+ NO_RECORD_DEF },
+ { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF },
+ { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(iapdRecords) },
+ { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+ RECORD_DEF(iaPrefixRecords) },
+ { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
+ RECORD_DEF(geoconfCivicRecords) },
+ { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+ RECORD_DEF(remoteIdRecords) },
+ { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF },
+ { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+ RECORD_DEF(clientFqdnRecords) },
+ { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF },
+ { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF },
+ { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
+ RECORD_DEF(lqQueryRecords) },
+ { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+ { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
+ RECORD_DEF(lqRelayData) },
+ { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF }
+
+ // @todo There is still a bunch of options for which we have to provide
+ // definitions but we don't do it because they are not really
+ // critical right now.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE6 =
+ sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index e66d700..5799d58 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
libdhcp___unittests_SOURCES += option6_int_unittest.cc
+libdhcp___unittests_SOURCES += option_data_types_unittest.cc
libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index af3d6ca..8ead521 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -68,8 +69,8 @@ public:
const std::type_info& expected_type) {
// Get all option definitions, we will use them to extract
// the definition for a particular option code.
- // We don't have to initialize option deinitions here because they
- // are initialized in the class'es constructor.
+ // We don't have to initialize option definitions here because they
+ // are initialized in the class's constructor.
OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
// Get the container index #1. This one allows for searching
// option definitions using option code.
@@ -90,13 +91,15 @@ public:
ASSERT_NO_THROW(def->validate());
OptionPtr option;
// Create the option.
- ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf))
+ << "Option creation failed to option code " << code;
// Make sure it is not NULL.
ASSERT_TRUE(option);
// And the actual object type is the one that we expect.
// Note that for many options there are dedicated classes
// derived from Option class to represent them.
- EXPECT_TRUE(typeid(*option) == expected_type);
+ EXPECT_TRUE(typeid(*option) == expected_type)
+ << "Invalid class returned for option code " << code;
}
};
@@ -399,24 +402,149 @@ TEST_F(LibDhcpTest, unpackOptions4) {
// This test have to be extended once all option definitions are
// created.
TEST_F(LibDhcpTest, stdOptionDefs6) {
- LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
- typeid(Option6IA));
- LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
- typeid(Option6IAAddr));
- LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
- typeid(Option6IntArray<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
- typeid(Option6Int<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
- typeid(Option6AddrLst));
+
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+
+ // Prepare buffer holding an array of FQDNs.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+ // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+ // to add the uint8_t value to it and then append the buffer
+ // holding some valid FQDN.
+ std::vector<uint8_t> client_fqdn_buf(1);
+ client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+ fqdn_buf.end());
+
+ // The actual test starts here for all supported option codes.
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, buf, typeid(Option6IAAddr));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ORO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, buf,
+ typeid(Option6Int<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+ buf, typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, buf,typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, buf,
+ typeid(Option6AddrLst));
}
}
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 1ee618a..24f101d 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -109,7 +109,13 @@ TEST_F(Option6IATest, basic) {
}
TEST_F(Option6IATest, simple) {
- Option6IA * ia = new Option6IA(D6O_IA_NA, 1234);
+ Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+
+ // Check that the values are really different than what we are about
+ // to set them to.
+ EXPECT_NE(2345, ia->getT1());
+ EXPECT_NE(3456, ia->getT2());
+
ia->setT1(2345);
ia->setT2(3456);
@@ -119,6 +125,9 @@ TEST_F(Option6IATest, simple) {
EXPECT_EQ(2345, ia->getT1());
EXPECT_EQ(3456, ia->getT2());
+ ia->setIAID(890);
+ EXPECT_EQ(890, ia->getIAID());
+
EXPECT_NO_THROW(
delete ia;
);
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 34969ee..044279b 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
EXPECT_EQ(Option::V4, option->getUniverse());
EXPECT_EQ(232, option->getType());
+
+ // Try to create an option using 'empty data' constructor
+ OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def3, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
}
// The purpose of this test is to verify that 'empty' option definition can
@@ -113,9 +124,10 @@ TEST_F(OptionCustomTest, constructor) {
TEST_F(OptionCustomTest, emptyData) {
OptionDefinition opt_def("OPTION_FOO", 232, "empty");
+ OptionBuffer buf;
boost::scoped_ptr<OptionCustom> option;
ASSERT_NO_THROW(
- option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
);
ASSERT_TRUE(option);
@@ -159,8 +171,10 @@ TEST_F(OptionCustomTest, binaryData) {
EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
// Check that option with "no data" is rejected.
+ buf_in.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+ buf_in.end())),
isc::OutOfRange
);
}
@@ -197,12 +211,46 @@ TEST_F(OptionCustomTest, booleanData) {
EXPECT_FALSE(value);
// Check that the option with "no data" is rejected.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 4)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
// The purpose of this test is to verify that the option definition comprising
// 16-bit signed integer value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, int16Data) {
@@ -338,6 +386,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
);
}
+
// The purpose of this test is to verify that the option definition comprising
// string value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, stringData) {
@@ -365,8 +414,9 @@ TEST_F(OptionCustomTest, stringData) {
EXPECT_EQ("hello world!", value);
// Check that option will not be created if empty buffer is provided.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -419,8 +469,9 @@ TEST_F(OptionCustomTest, booleanDataArray) {
// Check that empty buffer can't be used to create option holding
// array of boolean values.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -472,7 +523,6 @@ TEST_F(OptionCustomTest, uint32DataArray) {
buf.begin() + 3)),
isc::OutOfRange
);
-
}
// The purpose of this test is to verify that the option definition comprising
@@ -575,6 +625,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
);
}
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Create a buffer that holds two FQDNs.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Create an option from using a buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that two FQDN values have been extracted
+ // from a buffer.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Validate both values.
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ std::string domain1 = option->readFqdn(1);
+ EXPECT_EQ("example.com.", domain1);
+}
+
// The purpose of this test is to verify that the option definition comprising
// a record of various data fields can be used to create an instance of
// custom option.
@@ -584,20 +673,30 @@ TEST_F(OptionCustomTest, recordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
OptionBuffer buf;
- // Initialize field 0.
+ // Initialize field 0 to 8712.
writeInt<uint16_t>(8712, buf);
// Initialize field 1 to 'true'
buf.push_back(static_cast<unsigned short>(1));
- // Initialize field 2 to IPv4 address.
+ // Initialize field 2 to 'mydomain.example.com'.
+ buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+ // Initialize field 3 to IPv4 address.
writeAddress(IOAddress("192.168.0.1"), buf);
- // Initialize field 3 to IPv6 address.
+ // Initialize field 4 to IPv6 address.
writeAddress(IOAddress("2001:db8:1::1"), buf);
- // Initialize field 4 to string value.
+ // Initialize field 5 to string value.
writeString("ABCD", buf);
boost::scoped_ptr<OptionCustom> option;
@@ -606,8 +705,8 @@ TEST_F(OptionCustomTest, recordData) {
);
ASSERT_TRUE(option);
- // We should have 5 data fields.
- ASSERT_EQ(5, option->getDataFieldsNum());
+ // We should have 6 data fields.
+ ASSERT_EQ(6, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
@@ -620,19 +719,24 @@ TEST_F(OptionCustomTest, recordData) {
EXPECT_TRUE(value1);
// Verify value in the field 2.
- IOAddress value2("127.0.0.1");
- ASSERT_NO_THROW(value2 = option->readAddress(2));
- EXPECT_EQ("192.168.0.1", value2.toText());
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
// Verify value in the field 3.
- IOAddress value3("::1");
+ IOAddress value3("127.0.0.1");
ASSERT_NO_THROW(value3 = option->readAddress(3));
- EXPECT_EQ("2001:db8:1::1", value3.toText());
+ EXPECT_EQ("192.168.0.1", value3.toText());
// Verify value in the field 4.
- std::string value4;
- ASSERT_NO_THROW(value4 = option->readString(4));
- EXPECT_EQ("ABCD", value4);
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ std::string value5;
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ("ABCD", value5);
}
// The purpose of this test is to verify that truncated buffer
@@ -683,6 +787,413 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
);
}
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "binary");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default binary value.
+ OptionBuffer buf;
+ ASSERT_NO_THROW(option->readBinary());
+ // The buffer is by default empty.
+ EXPECT_TRUE(buf.empty());
+ // Prepare input buffer with some dummy data.
+ OptionBuffer buf_in(10);
+ for (int i = 0; i < buf_in.size(); ++i) {
+ buf_in[i] = i;
+ }
+ // Try to override the default binary buffer.
+ ASSERT_NO_THROW(option->writeBinary(buf_in));
+ // And check that it has been actually overriden.
+ ASSERT_NO_THROW(buf = option->readBinary());
+ ASSERT_EQ(buf_in.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Check that the default boolean value is false.
+ bool value = false;
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_FALSE(value);
+ // Check that we can override the default value.
+ ASSERT_NO_THROW(option->writeBoolean(true));
+ // Finally, check that it has been actually overriden.
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+ // Create a definition of an option that holds single
+ // uint32 value.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The default value for integer data fields is 0.
+ uint32_t value = 0;
+ ASSERT_NO_THROW(option->readInteger<uint32_t>());
+ EXPECT_EQ(0, value);
+
+ // Try to set the data field value to something different
+ // than 0.
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+ // Verify that it has been set.
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+ EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("127.0.0.1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("0.0.0.0", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("::1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("::", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default value of the option.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString());
+ // By default the string data field is empty.
+ EXPECT_TRUE(value.empty());
+ // Write some text to this field.
+ ASSERT_NO_THROW(option->writeString("hello world"));
+ // Check that it has been actually written.
+ EXPECT_NO_THROW(value = option->readString());
+ EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overriden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Read a default FQDN value from the option.
+ std::string fqdn;
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ(".", fqdn);
+ // Try override the default FQDN value.
+ ASSERT_NO_THROW(option->writeFqdn("example.com"));
+ // Check that the value has been actually overriden.
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add some boolean values to it.
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+ ASSERT_NO_THROW(option->addArrayDataField(false));
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+
+ // Verify that the new data fields can be added.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ bool value2 = false;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint16", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new data fields holding integer values.
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+ // We should now have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that the values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(67, value0);
+ uint16_t value1;
+ ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+ EXPECT_EQ(876, value1);
+ uint16_t value2;
+ ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+ EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Expect that the array does not contain any data fields yet.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 IPv4 addresses.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that all IP addresses have been set correctly.
+ IOAddress address0("127.0.0.1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("192.168.0.1", address0.toText());
+ IOAddress address1("127.0.0.1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("192.168.0.2", address1.toText());
+ IOAddress address2("127.0.0.1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("192.168.0.3", address2.toText());
+
+ // Add invalid address (IPv6 instead of IPv4).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("2001:db8:1::1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 addresses into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that they have correct values set.
+ IOAddress address0("::1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("2001:db8:1::1", address0.toText());
+ IOAddress address1("::1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("2001:db8:1::2", address1.toText());
+ IOAddress address2("::1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+ // Add invalid address (IPv4 instead of IPv6).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("192.168.0.1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(6, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ std::string value5 = "xyz";
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_TRUE(value5.empty());
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writeString("hello world", 5));
+
+ // Check that the new values have been correctly set.
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ(value5, "hello world");
+}
+
// The purpose of this test is to verify that pack function for
// DHCPv4 custom option works correctly.
TEST_F(OptionCustomTest, pack4) {
@@ -901,6 +1412,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
}
-
-
} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
new file mode 100644
index 0000000..748a84b
--- /dev/null
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -0,0 +1,491 @@
+// 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 <config.h>
+#include <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ OptionDataTypesTest() { }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ short family = address.getFamily();
+ if (family == AF_INET) {
+ asio::ip::address_v4::bytes_type buf_addr =
+ address.getAddress().to_v4().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+ } else if (family == AF_INET6) {
+ asio::ip::address_v6::bytes_type buf_addr =
+ address.getAddress().to_v6().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+ }
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+ // Create some IPv4 address.
+ asiolink::IOAddress address("192.168.0.1");
+ // And store it in a buffer in a wire format.
+ std::vector<uint8_t> buf;
+ writeAddress(address, buf);
+
+ // Now, try to read the IP address with a utility function
+ // being under test.
+ asiolink::IOAddress address_out("127.0.0.1");
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+ // Check that the read address matches address that
+ // we used as input.
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Check that an attempt to read the buffer as IPv6 address
+ // causes an error as the IPv6 address needs at least 16 bytes
+ // long buffer.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Do another test like this for IPv6 address.
+ address = asiolink::IOAddress("2001:db8:1:0::1");
+ writeAddress(address, buf);
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Truncate the buffer and expect an error to be reported when
+ // trying to read it.
+ buf.resize(buf.size() - 1);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+ // Encode an IPv6 address 2001:db8:1::1 in wire format.
+ // This will be used as reference data to validate if
+ // an IPv6 address is stored in a buffer properly.
+ const uint8_t data[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+ };
+ std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+ // Create IPv6 address object.
+ asiolink::IOAddress address("2001:db8:1::1");
+ // Define the output buffer to write IP address to.
+ std::vector<uint8_t> buf_out;
+ // Write the address to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ // Make sure that input and output buffers have the same size
+ // so we can compare them.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ // And finally compare them.
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ buf_out.clear();
+
+ // Do similar test for IPv4 address.
+ address = asiolink::IOAddress("192.168.0.1");
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ ASSERT_EQ(4, buf_out.size());
+ // Verify that the IP address has been written correctly.
+ EXPECT_EQ(192, buf_out[0]);
+ EXPECT_EQ(168, buf_out[1]);
+ EXPECT_EQ(0, buf_out[2]);
+ EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+ // Prepare the reference data.
+ const char data[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+ 0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+ // Create empty vector where binary data will be written to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(
+ OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+ );
+ // Verify that the buffer contains valid data.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that the boolean value stored
+// in a buffer is correctly read from this buffer.
+TEST_F(OptionDataTypesTest, readBool) {
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // 'true' value is encoded as 1 ('false' is encoded as 0)
+ buf.push_back(1);
+
+ // Read the value from the buffer.
+ bool value = false;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ // Verify the value.
+ EXPECT_TRUE(value);
+ // Check if 'false' is read correctly either.
+ buf[0] = 0;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ EXPECT_FALSE(value);
+
+ // Check that invalid value causes exception.
+ buf[0] = 5;
+ ASSERT_THROW(
+ OptionDataTypeUtil::readBool(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+ // Create a buffer we will write to.
+ std::vector<uint8_t> buf;
+ // Write the 'true' value to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+ // We should now have 'true' value stored in a buffer.
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(buf[0], 1);
+ // Let's append another value to make sure that it is not always
+ // 'true' value being written.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+ ASSERT_EQ(2, buf.size());
+ // Check that the first value has not changed.
+ EXPECT_EQ(buf[0], 1);
+ // Check the the second value is correct.
+ EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+ std::vector<uint8_t> buf;
+
+ // Write an 8-bit unsigned integer value to the buffer.
+ writeInt<uint8_t>(129, buf);
+ uint8_t valueUint8 = 0;
+ // Read the value and check that it is valid.
+ ASSERT_NO_THROW(
+ valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+ );
+ EXPECT_EQ(129, valueUint8);
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Clear the buffer for the next check we are going to do.
+ buf.clear();
+
+ // Test uint16_t value.
+ writeInt<uint16_t>(1234, buf);
+ uint16_t valueUint16 = 0;
+ ASSERT_NO_THROW(
+ valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+ );
+ EXPECT_EQ(1234, valueUint16);
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Test uint32_t value.
+ writeInt<uint32_t>(56789, buf);
+ uint32_t valueUint32 = 0;
+ ASSERT_NO_THROW(
+ valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+ );
+ EXPECT_EQ(56789, valueUint32);
+ buf.clear();
+
+ // Test int8_t value.
+ writeInt<int8_t>(-65, buf);
+ int8_t valueInt8 = 0;
+ ASSERT_NO_THROW(
+ valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+ );
+ EXPECT_EQ(-65, valueInt8);
+ buf.clear();
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int16_t value.
+ writeInt<int16_t>(2345, buf);
+ int32_t valueInt16 = 0;
+ ASSERT_NO_THROW(
+ valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+ );
+ EXPECT_EQ(2345, valueInt16);
+ buf.clear();
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int32_t value.
+ writeInt<int32_t>(-16543, buf);
+ int32_t valueInt32 = 0;
+ ASSERT_NO_THROW(
+ valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+ );
+ EXPECT_EQ(-16543, valueInt32);
+
+ buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+ // Prepare the reference buffer.
+ const uint8_t data[] = {
+ 0x7F, // 127
+ 0x03, 0xFF, // 1023
+ 0x00, 0x00, 0x10, 0x00, // 4096
+ 0xFF, 0xFF, 0xFC, 0x00, // -1024
+ 0x02, 0x00, // 512
+ 0x81 // -127
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+ // Fill in the buffer with data. Each write operation appends an
+ // integer value. Eventually the buffer holds all values and should
+ // match with the reference buffer.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+ // Make sure that the buffer has the same size as the reference
+ // buffer.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ // Compare buffers.
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+ // The binary representation of the "mydomain.example.com".
+ // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+ // labels within the FQDN.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Make a vector out of the data.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Read the buffer as FQDN and verify its correctness.
+ std::string fqdn;
+ EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+ EXPECT_EQ("mydomain.example.com.", fqdn);
+
+ // By resizing the buffer we simulate truncation. The first
+ // length field (8) indicate that the first label's size is
+ // 8 but the actual buffer size is 5. Expect that conversion
+ // fails.
+ buf.resize(5);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Another special case: provide an empty buffer.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+ // Create empty buffer. The FQDN will be written to it.
+ OptionBuffer buf;
+ // Write a domain name into the buffer in the format described
+ // in RFC1035 section 3.1. This function should not throw
+ // exception because domain name is well formed.
+ EXPECT_NO_THROW(
+ OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+ );
+ // The length of the data is 22 (8 bytes for "mydomain" label,
+ // 7 bytes for "example" label, 3 bytes for "com" label and
+ // finally 4 bytes positions between labels where length
+ // information is stored.
+ ASSERT_EQ(22, buf.size());
+
+ // Verify that length fields between labels hold valid values.
+ EXPECT_EQ(8, buf[0]); // length of "mydomain"
+ EXPECT_EQ(7, buf[9]); // length of "example"
+ EXPECT_EQ(3, buf[17]); // length of "com"
+ EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+ // Verify that labels are valid.
+ std::string label0(buf.begin() + 1, buf.begin() + 9);
+ EXPECT_EQ("mydomain", label0);
+
+ std::string label1(buf.begin() + 10, buf.begin() + 17);
+ EXPECT_EQ("example", label1);
+
+ std::string label2(buf.begin() + 18, buf.begin() + 21);
+ EXPECT_EQ("com", label2);
+
+ // The tested function is supposed to append data to a buffer
+ // so let's check that it is a case by appending another domain.
+ OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+ // The buffer length should be now longer.
+ ASSERT_EQ(33, buf.size());
+
+ // Check the length fields for new labels being appended.
+ EXPECT_EQ(5, buf[22]);
+ EXPECT_EQ(3, buf[28]);
+
+ // And check that labels are ok.
+ std::string label3(buf.begin() + 23, buf.begin() + 28);
+ EXPECT_EQ("hello", label3);
+
+ std::string label4(buf.begin() + 29, buf.begin() + 32);
+ EXPECT_EQ("net", label4);
+
+ // Check that invalid (empty) FQDN is rejected and expected
+ // exception type is thrown.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check another invalid domain name (with repeated dot).
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("example..com", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+ // Prepare a buffer with some string in it.
+ std::vector<uint8_t> buf;
+ writeString("hello world", buf);
+
+ // Read the string from the buffer.
+ std::string value;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readString(buf);
+ );
+ // Check that it is valid.
+ EXPECT_EQ("hello world", value);
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+ // Prepare a buffer with a reference data.
+ std::vector<uint8_t> buf_ref;
+ writeString("hello world!", buf_ref);
+ // Create empty buffer we will write to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+ // Compare two buffers.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 20c87d6..91b822c 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -23,6 +23,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
#include <exceptions/exceptions.h>
@@ -883,7 +884,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
);
ASSERT_TRUE(option_v6);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
std::vector<uint8_t> data = option_v6->getData();
std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
+ values[0].length());
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 50d143f..afa64d5 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -524,4 +524,27 @@ TEST_F(OptionTest, setData) {
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
buf_.size()));
}
+
+// This test verifies that options can be compared using equal() method.
+TEST_F(OptionTest, equal) {
+
+ // five options with varying lengths
+ OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
+ OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+ OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
+
+ // the same content as opt2, but different type
+ OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
+
+ // another instance with the same type and content as opt2
+ OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+
+ EXPECT_TRUE(opt1->equal(opt1));
+
+ EXPECT_FALSE(opt1->equal(opt2));
+ EXPECT_FALSE(opt1->equal(opt3));
+ EXPECT_FALSE(opt1->equal(opt4));
+
+ EXPECT_TRUE(opt2->equal(opt5));
+}
}
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index b2dd16a..cdaad3b 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -170,6 +170,8 @@ TEST_F(Pkt6Test, packUnpack) {
delete clone;
}
+// This test verifies that options can be added (addOption()), retrieved
+// (getOption(), getOptions()) and deleted (delOption()).
TEST_F(Pkt6Test, addGetDelOptions) {
Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
@@ -190,6 +192,25 @@ TEST_F(Pkt6Test, addGetDelOptions) {
// now there are 2 options of type 2
parent->addOption(opt3);
+ Option::OptionCollection options = parent->getOptions(2);
+ EXPECT_EQ(2, options.size()); // there should be 2 instances
+
+ // both options must be of type 2 and there must not be
+ // any other type returned
+ for (Option::OptionCollection::const_iterator x= options.begin();
+ x != options.end(); ++x) {
+ EXPECT_EQ(2, x->second->getType());
+ }
+
+ // Try to get a single option. Normally for singular options
+ // it is better to use getOption(), but getOptions() must work
+ // as well
+ options = parent->getOptions(1);
+ ASSERT_EQ(1, options.size());
+
+ EXPECT_EQ(1, (*options.begin()).second->getType());
+ EXPECT_EQ(opt1, options.begin()->second);
+
// let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
@@ -205,6 +226,10 @@ TEST_F(Pkt6Test, addGetDelOptions) {
// let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
+ // Finally try to get a non-existent option
+ options = parent->getOptions(1234);
+ EXPECT_EQ(0, options.size());
+
delete parent;
}
@@ -234,4 +259,52 @@ TEST_F(Pkt6Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
+// This test verifies that getName() method returns proper
+// packet type names.
+TEST_F(Pkt6Test, getName) {
+ // Check all possible packet types
+ for (int itype = 0; itype < 256; ++itype) {
+ uint8_t type = itype;
+
+ switch (type) {
+ case DHCPV6_CONFIRM:
+ EXPECT_STREQ("CONFIRM", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_DECLINE:
+ EXPECT_STREQ("DECLINE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_INFORMATION_REQUEST:
+ EXPECT_STREQ("INFORMATION_REQUEST",
+ Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REBIND:
+ EXPECT_STREQ("REBIND", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RELEASE:
+ EXPECT_STREQ("RELEASE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RENEW:
+ EXPECT_STREQ("RENEW", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REQUEST:
+ EXPECT_STREQ("REQUEST", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_SOLICIT:
+ EXPECT_STREQ("SOLICIT", Pkt6::getName(type));
+ break;
+
+ default:
+ EXPECT_STREQ("UNKNOWN", Pkt6::getName(type));
+ }
+ }
+}
+
+
}
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 3c46b13..7dc5f55 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -101,6 +101,14 @@ void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
subnets4_.push_back(subnet);
}
+void CfgMgr::deleteSubnets4() {
+ subnets4_.clear();
+}
+
+void CfgMgr::deleteSubnets6() {
+ subnets6_.clear();
+}
+
CfgMgr::CfgMgr() {
}
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 65a33f0..ac1b3f5 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -104,9 +104,9 @@ public:
/// needed is a dynamic server reconfiguration - a use case that is not
/// planned to be supported any time soon.
- /// @brief removes all subnets
+ /// @brief removes all IPv6 subnets
///
- /// This method removes all existing subnets. It is used during
+ /// This method removes all existing IPv6 subnets. It is used during
/// reconfiguration - old configuration is wiped and new definitions
/// are used to recreate subnets.
///
@@ -114,9 +114,7 @@ public:
/// between old and new configuration is tricky. For example: is
/// 2000::/64 and 2000::/48 the same subnet or is it something
/// completely new?
- void deleteSubnets6() {
- subnets6_.clear();
- }
+ void deleteSubnets6();
/// @brief get IPv4 subnet by address
///
@@ -133,7 +131,16 @@ public:
void addSubnet4(const Subnet4Ptr& subnet);
/// @brief removes all IPv4 subnets
- void removeSubnets4();
+ ///
+ /// This method removes all existing IPv4 subnets. It is used during
+ /// reconfiguration - old configuration is wiped and new definitions
+ /// are used to recreate subnets.
+ ///
+ /// @todo Implement more intelligent approach. Note that comparison
+ /// between old and new configuration is tricky. For example: is
+ /// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
+ /// completely new?
+ void deleteSubnets4();
protected:
/// @brief Protected constructor.
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index f954bed..a3b3b0c 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -1,5 +1,5 @@
/**
- @page dhcp-database-backends DHCP Database Back-Ends
+ @page dhcpDatabaseBackends DHCP Database Back-Ends
All DHCP lease data is stored in some form of database, the interface
to this being through the Lease Manager.
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 35586fe..99b433f 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -87,8 +87,7 @@ public:
///
/// Any character can be stored in the valid range of the region.
/// In particular, there can be a nul character (\0) in the middle of
- /// the region. On the other hand, it is not ensured that the string
- /// is nul-terminated. So the usual string manipulation API may not work
+ /// the region. So the usual string manipulation API may not work
/// as expected.
///
/// The `MasterLexer` implementation ensures that there are at least
diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py
index 516bea4..ca7a61e 100644
--- a/src/lib/dns/python/tests/tsigkey_python_test.py
+++ b/src/lib/dns/python/tests/tsigkey_python_test.py
@@ -170,6 +170,12 @@ class TSIGKeyRingTest(unittest.TestCase):
self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
self.assertEqual(self.secret, key.get_secret())
+ (code, key) = self.keyring.find(self.key_name)
+ self.assertEqual(TSIGKeyRing.SUCCESS, code)
+ self.assertEqual(self.key_name, key.get_key_name())
+ self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
+ self.assertEqual(self.secret, key.get_secret())
+
(code, key) = self.keyring.find(Name('different-key.example'),
self.sha256_name)
self.assertEqual(TSIGKeyRing.NOTFOUND, code)
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
index cf79c1a..bcab59c 100644
--- a/src/lib/dns/python/tsigkey_python.cc
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -287,7 +287,9 @@ PyMethodDef TSIGKeyRing_methods[] = {
METH_VARARGS,
"Remove a TSIGKey for the given name from the TSIGKeyRing." },
{ "find", reinterpret_cast<PyCFunction>(TSIGKeyRing_find), METH_VARARGS,
- "Find a TSIGKey for the given name in the TSIGKeyRing. "
+ "Find a TSIGKey for the given name in the TSIGKeyRing. Optional "
+ "second argument is an algorithm, in which case it only returns "
+ "a key if both match.\n"
"It returns a tuple of (result_code, key)." },
{ NULL, NULL, 0, NULL }
};
@@ -362,13 +364,16 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
PyObject*
TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
PyObject* key_name;
- PyObject* algorithm_name;
+ PyObject* algorithm_name = NULL;
- if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
+ if (PyArg_ParseTuple(args, "O!|O!", &name_type, &key_name,
&name_type, &algorithm_name)) {
- const TSIGKeyRing::FindResult result =
- self->cppobj->find(PyName_ToName(key_name),
- PyName_ToName(algorithm_name));
+ // Can't init TSIGKeyRing::FindResult without actual result,
+ // so use ternary operator
+ TSIGKeyRing::FindResult result = (algorithm_name == NULL) ?
+ self->cppobj->find(PyName_ToName(key_name)) :
+ self->cppobj->find(PyName_ToName(key_name),
+ PyName_ToName(algorithm_name));
if (result.key != NULL) {
s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
if (key == NULL) {
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
index 20ee802..c1367be 100644
--- a/src/lib/dns/tests/tsigkey_unittest.cc
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -251,6 +251,15 @@ TEST_F(TSIGKeyRingTest, find) {
const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result3.key);
+
+ // But with just the name it should work
+ const TSIGKeyRing::FindResult result4(keyring.find(key_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code);
+ EXPECT_EQ(key_name, result4.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
+ result4.key->getSecret(),
+ result4.key->getSecretLength());
}
TEST_F(TSIGKeyRingTest, findFromSome) {
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index d7d60eb..e55cce3 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -51,7 +51,7 @@ namespace {
if (name == TSIGKey::HMACSHA512_NAME()) {
return (isc::cryptolink::SHA512);
}
-
+
return (isc::cryptolink::UNKNOWN_HASH);
}
}
@@ -270,6 +270,16 @@ TSIGKeyRing::remove(const Name& key_name) {
}
TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) const {
+ TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+ impl_->keys.find(key_name);
+ if (found == impl_->keys.end()) {
+ return (FindResult(NOTFOUND, NULL));
+ }
+ return (FindResult(SUCCESS, &((*found).second)));
+}
+
+TSIGKeyRing::FindResult
TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
impl_->keys.find(key_name);
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
index 1bbd3fe..b10660c 100644
--- a/src/lib/dns/tsigkey.h
+++ b/src/lib/dns/tsigkey.h
@@ -327,6 +327,27 @@ public:
/// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
///
/// It searches the internal storage for a \c TSIGKey whose name is
+ /// \c key_name.
+ /// It returns the result in the form of a \c FindResult
+ /// object as follows:
+ /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+ /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+ /// otherwise \c NULL.
+ ///
+ /// The pointer returned in the \c FindResult object is only valid until
+ /// the corresponding key is removed from the key ring.
+ /// The caller must ensure that the key is held in the key ring while
+ /// it needs to refer to it, or it must make a local copy of the key.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be found.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult find(const Name& key_name) const;
+
+ /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+ ///
+ /// It searches the internal storage for a \c TSIGKey whose name is
/// \c key_name and that uses the hash algorithm identified by
/// \c algorithm_name.
/// It returns the result in the form of a \c FindResult
@@ -346,6 +367,7 @@ public:
/// \param algorithm_name The name of the algorithm of the found key.
/// \return A \c FindResult object enclosing the search result (see above).
FindResult find(const Name& key_name, const Name& algorithm_name) const;
+
private:
struct TSIGKeyRingImpl;
TSIGKeyRingImpl* impl_;
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 6207655..56918e9 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -31,6 +31,7 @@ libb10_log_la_SOURCES += message_initializer.cc message_initializer.h
libb10_log_la_SOURCES += message_reader.cc message_reader.h
libb10_log_la_SOURCES += message_types.h
libb10_log_la_SOURCES += output_option.cc output_option.h
+libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
EXTRA_DIST = README
EXTRA_DIST += logimpl_messages.mes
diff --git a/src/lib/log/README b/src/lib/log/README
index 3693abb..2a7af28 100644
--- a/src/lib/log/README
+++ b/src/lib/log/README
@@ -338,7 +338,8 @@ Variant #1, Used by Production Programs
---------------------------------------
void isc::log::initLogger(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
This is the call that should be used by production programs:
@@ -359,6 +360,17 @@ file
The name of a local message file. This will be read and its definitions used
to replace the compiled-in text of the messages.
+buffer
+If set to true, initial log messages will be internally buffered, until the
+first time a logger specification is processed. This way the program can
+use logging before even processing its logging configuration. As soon as any
+specification is processed (even an empty one), the buffered log messages will
+be flushed according to the specification. Note that if this option is used,
+the program SHOULD call one of the LoggerManager's process() calls (if you
+are using the built-in logging configuration handling in ModuleCCSession,
+this is automatically handled). If the program exits before this is done,
+all log messages are dumped in a raw format to stdout (so that no messages
+get lost).
Variant #2, Used by Unit Tests
------------------------------
diff --git a/src/lib/log/buffer_appender_impl.cc b/src/lib/log/buffer_appender_impl.cc
new file mode 100644
index 0000000..8899c4f
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.cc
@@ -0,0 +1,96 @@
+// 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/buffer_appender_impl.h>
+
+#include <log4cplus/loglevel.h>
+#include <boost/scoped_ptr.hpp>
+#include <cstdio>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+BufferAppender::~BufferAppender() {
+ // If there is anything left in the buffer,
+ // it means no reconfig has been done, and
+ // we can assume the logging system was either
+ // never setup, or broke while doing so.
+ // So dump all that is left to stdout
+ try {
+ flushStdout();
+ } catch (...) {
+ // Ok if we can't even seem to dump to stdout, never mind.
+ }
+}
+
+void
+BufferAppender::flushStdout() {
+ // This does not show a bit of information normal log messages
+ // do, so perhaps we should try and setup a new logger here
+ // However, as this is called from a destructor, it may not
+ // be a good idea; as we can't reliably know whether in what
+ // state the logger instance is now (or what the specific logger's
+ // settings were).
+ LogEventList::const_iterator it;
+ for (it = stored_.begin(); it != stored_.end(); ++it) {
+ const std::string level(it->first);
+ LogEventPtr event(it->second);
+ std::printf("%s [%s]: %s\n", level.c_str(),
+ event->getLoggerName().c_str(),
+ event->getMessage().c_str());
+ }
+ stored_.clear();
+}
+
+void
+BufferAppender::flush() {
+ LogEventList stored_copy;
+ stored_.swap(stored_copy);
+
+ LogEventList::const_iterator it;
+ for (it = stored_copy.begin(); it != stored_copy.end(); ++it) {
+ LogEventPtr event(it->second);
+ log4cplus::Logger logger =
+ log4cplus::Logger::getInstance(event->getLoggerName());
+
+ logger.log(event->getLogLevel(), event->getMessage());
+ }
+ flushed_ = true;
+}
+
+size_t
+BufferAppender::getBufferSize() const {
+ return (stored_.size());
+}
+
+void
+BufferAppender::append(const log4cplus::spi::InternalLoggingEvent& event) {
+ if (flushed_) {
+ isc_throw(LogBufferAddAfterFlush,
+ "Internal log buffer has been flushed already");
+ }
+ // get a clone, and put the pointer in a shared_ptr in the list
+ std::auto_ptr<log4cplus::spi::InternalLoggingEvent> event_aptr =
+ event.clone();
+ // Also store the string representation of the log level, to be
+ // used in flushStdout if necessary
+ stored_.push_back(LevelAndEvent(
+ log4cplus::LogLevelManager().toString(event.getLogLevel()),
+ LogEventPtr(event_aptr.release())));
+}
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
diff --git a/src/lib/log/buffer_appender_impl.h b/src/lib/log/buffer_appender_impl.h
new file mode 100644
index 0000000..00d3633
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.h
@@ -0,0 +1,118 @@
+// 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 LOG_BUFFER_H
+#define LOG_BUFFER_H
+
+#include <exceptions/exceptions.h>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/spi/loggingevent.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+/// \brief Buffer add after flush
+///
+/// This exception is thrown if the log buffer's add() method
+/// is called after the log buffer has been flushed; the buffer
+/// is only supposed to be used once (until the first time a
+/// logger specification is processed)
+class LogBufferAddAfterFlush : public isc::Exception {
+public:
+ LogBufferAddAfterFlush(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// Convenience typedef for a pointer to a log event
+typedef boost::shared_ptr<log4cplus::spi::InternalLoggingEvent> LogEventPtr;
+
+/// Convenience typedef for a pair string/logeventptr, the string representing
+/// the logger level, as returned by LogLevelManager::toString() at the
+/// time of initial logging
+typedef std::pair<std::string, LogEventPtr> LevelAndEvent;
+
+/// Convenience typedef for a vector of LevelAndEvent instances
+typedef std::vector<LevelAndEvent> LogEventList;
+
+/// \brief Buffering Logger Appender
+///
+/// This class can be set as an Appender for log4cplus loggers,
+/// and is used to store logging events; it simply keeps any
+/// event that is passed to \c append(), and will replay them to the
+/// logger that they were originally created for when \c flush() is
+/// called.
+///
+/// The idea is that initially, a program may want to do some logging,
+/// but does not know where to yet (for instance because it has yet to
+/// read and parse its configuration). Any log messages before this time
+/// would normally go to some default (say, stdout), and be lost in the
+/// real logging destination. By buffering them (and flushing them once
+/// the logger has been configured), these log messages are kept in a
+/// consistent place, and are not lost.
+///
+/// Given this goal, this class has an extra check; it will raise
+/// an exception if \c append() is called after flush().
+///
+/// If the BufferAppender instance is destroyed before being flushed,
+/// it will dump any event it has left to stdout.
+class BufferAppender : public log4cplus::Appender {
+public:
+ /// \brief Constructor
+ ///
+ /// Constructs a BufferAppender that buffers log evens
+ BufferAppender() : flushed_(false) {}
+
+ /// \brief Destructor
+ ///
+ /// Any remaining events are flushed to stdout (there should
+ /// only be any events remaining if flush() was never called)
+ virtual ~BufferAppender();
+
+ /// \brief Close the appender
+ ///
+ /// This class has no specialized handling for this method
+ virtual void close() {}
+
+ /// \brief Flush the internal buffer
+ ///
+ /// Events that have been stored (after calls to \c append()
+ /// are replayed to the logger. Should only be called after
+ /// new appenders have been set to the logger.
+ void flush();
+
+ /// \brief Returns the number of stored logging events
+ ///
+ /// Mainly useful for testing
+ size_t getBufferSize() const;
+
+protected:
+ virtual void append(const log4cplus::spi::InternalLoggingEvent& event);
+private:
+ /// \brief Helper for the destructor, flush events to stdout
+ void flushStdout();
+
+ LogEventList stored_;
+ bool flushed_;
+};
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
+
+#endif // LOG_BUFFER_H
+
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index 8431c2e..77893d0 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -94,7 +94,7 @@ LoggerManager::processEnd() {
void
LoggerManager::init(const std::string& root, isc::log::Severity severity,
- int dbglevel, const char* file)
+ int dbglevel, const char* file, bool buffer)
{
// Load in the messages declared in the program and registered by
// statically-declared MessageInitializer objects.
@@ -114,7 +114,9 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
// Initialize the implementation logging. After this point, some basic
// logging has been set up and messages can be logged.
- LoggerManagerImpl::init(severity, dbglevel);
+ // However, they will not appear until a logging specification has been
+ // processed (or the program exits), see TODO
+ LoggerManagerImpl::init(severity, dbglevel, buffer);
setLoggingInitialized();
// Check if there were any duplicate message IDs in the default dictionary
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
index 63699c9..da49ae4 100644
--- a/src/lib/log/logger_manager.h
+++ b/src/lib/log/logger_manager.h
@@ -76,6 +76,21 @@ public:
processEnd();
}
+ /// \brief Process 'empty' specification
+ ///
+ /// This will disable any existing output options, and set
+ /// the logging to go to the built-in default (stdout).
+ /// If the logger has been initialized with buffering enabled,
+ /// all log messages up to now shall be printed to stdout.
+ ///
+ /// This is mainly useful in scenarios where buffering is needed,
+ /// but it turns out there are no logging specifications to
+ /// handle.
+ void process() {
+ processInit();
+ processEnd();
+ }
+
/// \brief Run-Time Initialization
///
/// Performs run-time initialization of the logger system, in particular
@@ -91,13 +106,18 @@ public:
/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file. This must be NULL if there
/// is no local message file.
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void init(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
/// \brief Reset logging
///
- /// Resets logging to whatever was set in the call to init().
+ /// Resets logging to whatever was set in the call to init(), expect for
+ /// the buffer option.
static void reset();
/// \brief Read local message file
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 6357e1f..6862d0c 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -23,12 +23,14 @@
#include <log4cplus/syslogappender.h>
#include <log/logger.h>
+#include <log/logger_support.h>
#include <log/logger_level_impl.h>
#include <log/logger_manager.h>
#include <log/logger_manager_impl.h>
#include <log/log_messages.h>
#include <log/logger_name.h>
#include <log/logger_specification.h>
+#include <log/buffer_appender_impl.h>
using namespace std;
@@ -40,19 +42,24 @@ namespace log {
// passed back to the parent) and resets the root logger to logging
// informational messages. (This last is not a log4cplus default, so we have to
// explicitly reset the logging severity.)
-
void
LoggerManagerImpl::processInit() {
+ storeBufferAppenders();
+
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
initRootLogger();
}
+// Flush the BufferAppenders at the end of processing a new specification
+void
+LoggerManagerImpl::processEnd() {
+ flushBufferAppenders();
+}
+
// Process logging specification. Set up the common states then dispatch to
// add output specifications.
-
void
LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
-
log4cplus::Logger logger = log4cplus::Logger::getInstance(
expandLoggerName(spec.getName()));
@@ -65,8 +72,7 @@ LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
// Output options given?
if (spec.optionCount() > 0) {
-
- // Yes, so replace all appenders for this logger.
+ // Replace all appenders for this logger.
logger.removeAllAppenders();
// Now process output specifications.
@@ -134,7 +140,17 @@ LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
logger.addAppender(fileapp);
}
-// Syslog appender.
+void
+LoggerManagerImpl::createBufferAppender(log4cplus::Logger& logger) {
+ log4cplus::SharedAppenderPtr bufferapp(new internal::BufferAppender());
+ bufferapp->setName("buffer");
+ logger.addAppender(bufferapp);
+ // Since we do not know at what level the loggers will end up
+ // running, set it to the highest for now
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+}
+
+// Syslog appender.
void
LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt)
@@ -147,10 +163,10 @@ LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
// One-time initialization of the log4cplus system
-
void
-LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel,
+ bool buffer)
+{
// Set up basic configurator. This attaches a ConsoleAppender to the
// root logger with suitable output. This is used until we we have
// actually read the logging configuration, in which case the output
@@ -161,22 +177,22 @@ LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
// Add the additional debug levels
LoggerLevelImpl::init();
- reset(severity, dbglevel);
+ initRootLogger(severity, dbglevel, buffer);
}
// Reset logging to default configuration. This closes all appenders
// and resets the root logger to output INFO messages to the console.
// It is principally used in testing.
void
-LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel)
+{
// Initialize the root logger
initRootLogger(severity, dbglevel);
}
// Initialize the root logger
void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
- int dbglevel)
+ int dbglevel, bool buffer)
{
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
@@ -191,14 +207,14 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
b10root.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
Level(severity, dbglevel)));
- // Set the BIND 10 root to use a console logger.
- OutputOption opt;
- createConsoleAppender(b10root, opt);
+ if (buffer) {
+ createBufferAppender(b10root);
+ } else {
+ OutputOption opt;
+ createConsoleAppender(b10root, opt);
+ }
}
-// Set the the "console" layout for the given appenders. This layout includes
-// a date/time and the name of the logger.
-
void LoggerManagerImpl::setConsoleAppenderLayout(
log4cplus::SharedAppenderPtr& appender)
{
@@ -225,5 +241,31 @@ void LoggerManagerImpl::setSyslogAppenderLayout(
appender->setLayout(layout);
}
+void LoggerManagerImpl::storeBufferAppenders() {
+ // Walk through all loggers, and find any buffer appenders there
+ log4cplus::LoggerList loggers = log4cplus::Logger::getCurrentLoggers();
+ log4cplus::LoggerList::iterator it;
+ for (it = loggers.begin(); it != loggers.end(); ++it) {
+ log4cplus::SharedAppenderPtr buffer_appender =
+ it->getAppender("buffer");
+ if (buffer_appender) {
+ buffer_appender_store_.push_back(buffer_appender);
+ }
+ }
+}
+
+void LoggerManagerImpl::flushBufferAppenders() {
+ std::vector<log4cplus::SharedAppenderPtr> copy;
+ buffer_appender_store_.swap(copy);
+
+ std::vector<log4cplus::SharedAppenderPtr>::iterator it;
+ for (it = copy.begin(); it != copy.end(); ++it) {
+ internal::BufferAppender* app =
+ dynamic_cast<internal::BufferAppender*>(it->get());
+ assert(app != NULL);
+ app->flush();
+ }
+}
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h
index 2bce655..bcb2fc7 100644
--- a/src/lib/log/logger_manager_impl.h
+++ b/src/lib/log/logger_manager_impl.h
@@ -51,15 +51,14 @@ class LoggerManagerImpl {
public:
/// \brief Constructor
- LoggerManagerImpl()
- {}
+ LoggerManagerImpl() {}
/// \brief Initialize Processing
///
/// This resets the hierachy of loggers back to their defaults. This means
/// that all non-root loggers (if they exist) are set to NOT_SET, and the
/// root logger reset to logging informational messages.
- static void processInit();
+ void processInit();
/// \brief Process Specification
///
@@ -71,8 +70,7 @@ public:
/// \brief End Processing
///
/// Terminates the processing of the logging specifications.
- static void processEnd()
- {}
+ void processEnd();
/// \brief Implementation-specific initialization
///
@@ -87,8 +85,11 @@ public:
///
/// \param severity Severity to be associated with this logger
/// \param dbglevel Debug level associated with the root logger
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void init(isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0);
+ int dbglevel = 0, bool buffer = false);
/// \brief Reset logging
///
@@ -132,15 +133,27 @@ private:
static void createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt);
+ /// \brief Create buffered appender
+ ///
+ /// Appends an object to the logger that will store the log events sent
+ /// to the logger. These log messages are replayed to the logger in
+ /// processEnd().
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ static void createBufferAppender(log4cplus::Logger& logger);
+
/// \brief Set default layout and severity for root logger
///
- /// Initializes the root logger to BIND 10 defaults - console output and
- /// the passed severity/debug level.
+ /// Initializes the root logger to BIND 10 defaults - console or buffered
+ /// output and the passed severity/debug level.
///
/// \param severity Severity of messages that the logger should output.
/// \param dbglevel Debug level if severity = DEBUG
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void initRootLogger(isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0);
+ int dbglevel = 0, bool buffer = false);
/// \brief Set layout for console appender
///
@@ -161,6 +174,25 @@ private:
///
/// \param appender Appender for which this pattern is to be set.
static void setSyslogAppenderLayout(log4cplus::SharedAppenderPtr& appender);
+
+ /// \brief Store all buffer appenders
+ ///
+ /// When processing a new specification, this method can be used
+ /// to keep a list of the buffer appenders; the caller can then
+ /// process the specification, and call \c flushBufferAppenders()
+ /// to flush and clear the list
+ void storeBufferAppenders();
+
+ /// \brief Flush the stored buffer appenders
+ ///
+ /// This flushes the list of buffer appenders stored in
+ /// \c storeBufferAppenders(), and clears it
+ void flushBufferAppenders();
+
+ /// Only used between processInit() and processEnd(), to temporarily
+ /// store the buffer appenders in order to flush them after
+ /// processSpecification() calls have been completed
+ std::vector<log4cplus::SharedAppenderPtr> buffer_appender_store_;
};
} // namespace log
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index 2097136..cab52ad 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -46,8 +46,8 @@ setLoggingInitialized(bool state) {
void
initLogger(const string& root, isc::log::Severity severity, int dbglevel,
- const char* file) {
- LoggerManager::init(root, severity, dbglevel, file);
+ const char* file, bool buffer) {
+ LoggerManager::init(root, severity, dbglevel, file, buffer);
}
} // namespace log
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index f59be60..bd3b5b3 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -61,9 +61,13 @@ void setLoggingInitialized(bool state = true);
/// \param severity Severity at which to log
/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file.
+/// \param buffer If true, all log messages will be buffered until one of
+/// the \c process() methods is called. If false, initial logging
+/// shall go to the default output (i.e. stdout)
void initLogger(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
} // namespace log
} // namespace isc
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 7f96077..5683842 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -37,6 +37,15 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+noinst_PROGRAMS += buffer_logger_test
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+buffer_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+buffer_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
@@ -82,11 +91,13 @@ 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
+run_unittests_SOURCES += buffer_appender_unittest.cc
nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
run_unittests_LDADD = $(AM_LDADD)
+run_unittests_LDADD += $(LOG4CPLUS_LIBS)
run_unittests_LDFLAGS = $(AM_LDFLAGS)
# logging initialization tests. These are put in separate programs to
@@ -124,6 +135,7 @@ check-local:
$(SHELL) $(abs_builddir)/console_test.sh
$(SHELL) $(abs_builddir)/destination_test.sh
$(SHELL) $(abs_builddir)/init_logger_test.sh
+ $(SHELL) $(abs_builddir)/buffer_logger_test.sh
$(SHELL) $(abs_builddir)/local_file_test.sh
$(SHELL) $(abs_builddir)/logger_lock_test.sh
$(SHELL) $(abs_builddir)/severity_test.sh
@@ -131,6 +143,7 @@ check-local:
noinst_SCRIPTS = console_test.sh
noinst_SCRIPTS += destination_test.sh
noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += buffer_logger_test.sh
noinst_SCRIPTS += local_file_test.sh
noinst_SCRIPTS += logger_lock_test.sh
noinst_SCRIPTS += severity_test.sh
diff --git a/src/lib/log/tests/buffer_appender_unittest.cc b/src/lib/log/tests/buffer_appender_unittest.cc
new file mode 100644
index 0000000..781fcbe
--- /dev/null
+++ b/src/lib/log/tests/buffer_appender_unittest.cc
@@ -0,0 +1,146 @@
+// 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 "config.h"
+#include <gtest/gtest.h>
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/logger.h>
+#include <log4cplus/nullappender.h>
+#include <log4cplus/spi/loggingevent.h>
+
+using namespace isc::log;
+using namespace isc::log::internal;
+
+namespace isc {
+namespace log {
+
+/// \brief Specialized test class
+///
+/// In order to test append() somewhat directly, this
+/// class implements one more method (addEvent)
+class TestBufferAppender : public BufferAppender {
+public:
+ void addEvent(const log4cplus::spi::InternalLoggingEvent& event) {
+ append(event);
+ }
+};
+
+class BufferAppenderTest : public ::testing::Test {
+protected:
+ BufferAppenderTest() : buffer_appender1(new TestBufferAppender()),
+ appender1(buffer_appender1),
+ buffer_appender2(new TestBufferAppender()),
+ appender2(buffer_appender2),
+ logger(log4cplus::Logger::getInstance("buffer"))
+ {
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+ }
+
+ ~BufferAppenderTest() {
+ // If any log messages are left, we don't care, get rid of them,
+ // by flushing them to a null appender
+ // Given the 'messages should not get lost' approach of the logging
+ // system, not flushing them to a null appender would cause them
+ // to be dumped to stdout as the test is destroyed, making
+ // unnecessarily messy test output.
+ log4cplus::SharedAppenderPtr null_appender(
+ new log4cplus::NullAppender());
+ logger.removeAllAppenders();
+ logger.addAppender(null_appender);
+ buffer_appender1->flush();
+ buffer_appender2->flush();
+ }
+
+ TestBufferAppender* buffer_appender1;
+ log4cplus::SharedAppenderPtr appender1;
+ TestBufferAppender* buffer_appender2;
+ log4cplus::SharedAppenderPtr appender2;
+ log4cplus::Logger logger;
+};
+
+// Test that log events are indeed stored, and that they are
+// flushed to the new appenders of their logger
+TEST_F(BufferAppenderTest, flush) {
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Create a Logger, log a few messages with the first appender
+ logger.addAppender(appender1);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(3, buffer_appender1->getBufferSize());
+
+ // Second buffer should still be empty
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Replace the appender by the second one, and call flush;
+ // this should cause all events to be moved to the second buffer
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(3, buffer_appender2->getBufferSize());
+}
+
+// Once flushed, logging new messages with the same buffer should fail
+TEST_F(BufferAppenderTest, addAfterFlush) {
+ logger.addAppender(appender1);
+ buffer_appender1->flush();
+ EXPECT_THROW(LOG4CPLUS_INFO(logger, "Foo"), LogBufferAddAfterFlush);
+ // It should not have been added
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+
+ // But logging should work again as long as a different buffer is used
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender2->getBufferSize());
+}
+
+TEST_F(BufferAppenderTest, addDirectly) {
+ // A few direct calls
+ log4cplus::spi::InternalLoggingEvent event("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event);
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+
+ // Do one from a smaller scope to make sure destruction doesn't harm
+ {
+ log4cplus::spi::InternalLoggingEvent event2("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event2);
+ }
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+
+ // And flush them to the next
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(2, buffer_appender2->getBufferSize());
+}
+
+}
+}
diff --git a/src/lib/log/tests/buffer_logger_test.cc b/src/lib/log/tests/buffer_logger_test.cc
new file mode 100644
index 0000000..8d1b3cf
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.cc
@@ -0,0 +1,71 @@
+// 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/logger_manager.h>
+#include <log/log_messages.h>
+#include <util/interprocess_sync_null.h>
+
+using namespace isc::log;
+
+namespace {
+void usage() {
+ std::cout << "Usage: buffer_logger_test [-n]" << std::endl;
+}
+} // end unnamed namespace
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging with
+/// buffering enabled, so that initial log messages are not immediately
+/// logged, but are not lost (they should be logged the moment process()
+/// is called.
+///
+/// If -n is given as an argument, process() is never called. In this
+/// case, upon exit, all leftover log messages should be printed to
+/// stdout, but without normal logging additions (such as time and
+/// logger name)
+int
+main(int argc, char** argv) {
+ bool do_process = true;
+ int opt;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ switch (opt) {
+ case 'n':
+ do_process = false;
+ break;
+ default:
+ usage();
+ return (1);
+ }
+ }
+
+ // Note, level is INFO, so DEBUG should normally not show
+ // up. Unless process is never called (at which point it
+ // will end up in the dump at the end).
+ initLogger("buffertest", isc::log::INFO, 0, NULL, true);
+ Logger logger("log");
+ // No need for file interprocess locking in this test
+ logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ // process should cause them to be logged
+ if (do_process) {
+ LoggerManager logger_manager;
+ logger_manager.process();
+ }
+ return (0);
+}
diff --git a/src/lib/log/tests/buffer_logger_test.sh.in b/src/lib/log/tests/buffer_logger_test.sh.in
new file mode 100755
index 0000000..696f829
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.sh.in
@@ -0,0 +1,61 @@
+#!/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 initLogger() call uses for unit tests respects the setting of
+# the buffer value
+#
+
+testname="bufferLogger test"
+echo $testname
+
+failcount=0
+tempfile=@abs_builddir@/buffer_logger_test_tempfile_$$
+
+passfail() {
+ if [ $1 -eq 0 ]; then
+ echo " pass"
+ else
+ echo " FAIL"
+ failcount=`expr $failcount + $1`
+ fi
+}
+
+echo "1. Checking that buffer initialization works"
+
+echo -n " - Buffer including process() call: "
+cat > $tempfile << .
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test 2>&1 | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n " - Buffer excluding process() call: "
+cat > $tempfile << .
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+DEBUG [buffertest.log]: LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test -n 2>&1 | diff $tempfile -
+passfail $?
+
+
+
+# Tidy up.
+rm -f $tempfile
+
+exit $failcount
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index aa0547b..a200dfc 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -34,7 +34,7 @@ import bind10_config
import isc.log
from isc.log_messages.cfgmgr_messages import *
-logger = isc.log.Logger("cfgmgr")
+logger = isc.log.Logger("cfgmgr", buffer=True)
class ConfigManagerDataReadError(Exception):
"""This exception is thrown when there is an error while reading
@@ -224,8 +224,13 @@ class ConfigManager:
def check_logging_config(self, config):
if self.log_module_name in config:
+ # If there is logging config, apply it.
ccsession.default_logconfig_handler(config[self.log_module_name],
self.log_config_data)
+ else:
+ # If there is no logging config, we still need to trigger the
+ # handler, so make it use defaults (and flush any buffered logs)
+ ccsession.default_logconfig_handler({}, self.log_config_data)
def notify_boss(self):
"""Notifies the Boss module that the Config Manager is running"""
@@ -313,11 +318,11 @@ class ConfigManager:
self.config = ConfigManagerData.read_from_file(self.data_path,
self.\
database_filename)
- self.check_logging_config(self.config.data);
except ConfigManagerDataEmpty:
# ok, just start with an empty config
self.config = ConfigManagerData(self.data_path,
self.database_filename)
+ self.check_logging_config(self.config.data);
def write_config(self):
"""Write the current configuration to the file specificied at init()"""
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index 69e70b7..7b95201 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -166,17 +166,23 @@ reset(PyObject*, PyObject*) {
}
PyObject*
-init(PyObject*, PyObject* args) {
+init(PyObject*, PyObject* args, PyObject* arg_keywords) {
const char* root;
const char* file(NULL);
const char* severity("INFO");
+ bool buffer = false;
int dbglevel(0);
- if (!PyArg_ParseTuple(args, "s|siz", &root, &severity, &dbglevel, &file)) {
+ const char* const keywords[] = { "name", "severity", "debuglevel", "file",
+ "buffer", NULL };
+ if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb",
+ const_cast<char**>(keywords), &root,
+ &severity, &dbglevel, &file, &buffer)) {
return (NULL);
}
try {
- LoggerManager::init(root, getSeverity(severity), dbglevel, file);
+ LoggerManager::init(root, getSeverity(severity), dbglevel, file,
+ buffer);
}
catch (const std::exception& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -266,12 +272,19 @@ PyMethodDef methods[] = {
"need to call it. It returns None if the message does not exist."},
{"reset", reset, METH_NOARGS,
"Reset all logging. For testing purposes only, do not use."},
- {"init", init, METH_VARARGS,
+ {"init", reinterpret_cast<PyCFunction>(init), METH_VARARGS | METH_KEYWORDS,
"Run-time initialization. You need to call this before you do any "
"logging, to configure the root logger name. You may also provide "
- "logging severity (one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
- "'FATAL'), a debug level (integer in the range 0-99) and a file name "
- "of a dictionary with message text translations."},
+ "Arguments:\n"
+ "name: root logger name\n"
+ "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
+ "'FATAL'\n"
+ "debuglevel (optional): a debug level (integer in the range 0-99) "
+ "file (optional): a file name of a dictionary with message text "
+ "translations\n"
+ "buffer (optional), boolean, when True, causes all log messages "
+ "to be stored internally until log_config_update is called, at "
+ "which point they shall be logged."},
{"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS,
"Resets the configuration of the root logger to that set by the "
"B10_XXX environment variables. It is aimed at unit tests, where "
@@ -655,7 +668,7 @@ PyTypeObject logger_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/python/isc/log/tests/log_test.py b/src/lib/python/isc/log/tests/log_test.py
index 1337654..2059caa 100644
--- a/src/lib/python/isc/log/tests/log_test.py
+++ b/src/lib/python/isc/log/tests/log_test.py
@@ -56,6 +56,28 @@ class Manager(unittest.TestCase):
# ignore errors like missing file?
isc.log.init("root", "INFO", 0, "/no/such/file");
+ def test_init_keywords(self):
+ isc.log.init(name="root", severity="DEBUG", debuglevel=50, file=None,
+ buffer=True)
+ # unknown keyword
+ self.assertRaises(TypeError, isc.log.init, name="root", foo="bar")
+ # Replace the values for each keyword by a wrong type, one by one
+ self.assertRaises(TypeError, isc.log.init, name=1,
+ severity="DEBUG", debuglevel=50, file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity=2, debuglevel=50, file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel="50", file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel=50, file=1,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel=50, file=None,
+ buffer=None)
+
def test_log_config_update(self):
log_spec = json.dumps(isc.config.module_spec_from_file(path_search('logging.spec', bind10_config.PLUGIN_PATHS)).get_full_spec())
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 3c7fa1c..34674ca 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -79,7 +79,7 @@ Feature: Xfrin
When I send bind10 the following commands:
"""
config add tsig_keys/keys "example.key.:c2VjcmV0"
- config set Xfrin/zones[0]/tsig_key "example.key.:c2VjcmV0"
+ config set Xfrin/zones[0]/tsig_key "example.key."
config commit
"""
More information about the bind10-changes
mailing list