BIND 10 trac2956, updated. b222f7faadc7ed7898a936a2b97b2a385c6a2a1b [2956] Created the initial, implementation of DHCP-DDNS service controller class, D2Controller, the base class DControllerBase, and unit tests.
BIND 10 source code commits
bind10-changes at lists.isc.org
Sun Jun 2 11:52:26 UTC 2013
The branch, trac2956 has been updated
via b222f7faadc7ed7898a936a2b97b2a385c6a2a1b (commit)
via 80d16e0dfbaeb8053cbd29d6b0512d75351a2c25 (commit)
via fd911f4775865a204a9a67dfcf290d8395e0734d (commit)
via dbe4772246039a1257b6492936fda2a8600cd245 (commit)
via 72b428b6f9178a08d8e5bf68062b4082bf0b8429 (commit)
via 01e1910c9db5d1e5dcf2b5ea01afd7aa8075ebc4 (commit)
via ece829ff0b6b5117ecdfe22439c24b64a8523683 (commit)
via be23568ad3e10931387612fa90f8d2568e9878e7 (commit)
via 42073d9d8c264a33ca883c9e80310cb91de706c6 (commit)
via ea97070cf6b41299351fc29af66fa39c6465d56a (commit)
via a9b9f641970b7f2db1b1183fab3deaec454b5b75 (commit)
via d7fe718898c7a8d1fbcebd4a4d47548d08ae1d98 (commit)
via bf2a2aab27a29e23699f42c393ac31b324c7f096 (commit)
via 26f8bf358b2eceec472e341f1f380bf9474913de (commit)
via 4eb6ec7a740b1ec21334cc69b0272883c8e874cf (commit)
via 56ee9810fdfb5f86bd6948e6bf26545ac714edd8 (commit)
via aafb86a4203109142c6f888a2548fe57dd0dd701 (commit)
via bde0e94518469557c8b455ccbecc079a38382afd (commit)
via a29c0e18ea2b70d6e4c2b2bd48199240f4c909db (commit)
via 64bc0886402d587b65343fe6faecf35ed59eb029 (commit)
via 8ed9ddfe7f17d01d29cdaed8ee800487413e8632 (commit)
via b70a49c18a153f40aaf129d6d3f25039e052e887 (commit)
via e5094ef98ad34b001152d14ea2ea4c6ae9fbd85e (commit)
via 8244c9cb7152aa47be60e0f86f02289fd67a6239 (commit)
via e08a21c3bdc637a447d68da727fa3f4cb6a26112 (commit)
via 419690bf77f83061c4df46291b27a2a5769e1758 (commit)
via 12164374c127f1e9484c63f2f365ff7395768599 (commit)
via c2d40e3d425f1e51647be6a717c4a97d7ca3c29c (commit)
via 1248e88ace533f61e4f8865fafa5f0c89470c6cd (commit)
via e8a9043a2508edd209a01facb71fe4bac4d9eceb (commit)
via cd146974efff9794c989296158c0e05997da1edf (commit)
via c1d838b4ef7ed6a20b43987713981ff521aec274 (commit)
via 41aef4f8135de17e94f3d703ff8f461bfeedd94e (commit)
via e7e1564b312b70ed4d0c04e59025e337f0210729 (commit)
via 9b41a2252bf1db1fe686ed4cb8b20d3fbbf1d5e6 (commit)
via c13f4a4ab9ddf938d9b5fd62e08574dd8dc210b1 (commit)
via c82208ea06ca7d72186cc606cc4038bb35b7afc1 (commit)
via f34eec70bf82d42c27a0df4709b0682abf2f7b2a (commit)
via 454afa9877926214539d135f239035706bbec035 (commit)
via 4b68c9bb855e1cee112ed6a0c3dd398048930824 (commit)
via cddad16de73088a9e11330746e33a1faed947f64 (commit)
via 32931752aee5c1c61bf6758a3ad34a77eec04064 (commit)
via 9346c7a0f4d9dce50b673e66b5cc992f9b32d18b (commit)
via fafa0dd4a0c97aeef84b7c95ed624c3873ca4cb4 (commit)
via 85dff79c7ec72f5079dea7646baa557de2e98c5b (commit)
via 2ae9ac7bb5a19741eb21b0ccec951588c5672d1d (commit)
via e7591db5df9c7694e1e0e3f7dd3e2d77753999c1 (commit)
via 453f362d6c7bba06582e713b9fa243fa0b8ef5b7 (commit)
via 96b1a7eb31b16bf9b270ad3d82873c0bd86a3530 (commit)
via 704e1a50cae62f61fe2e09d5bcbf59d30c4aae97 (commit)
via 9a49846cb4456b17526c30799df03c9a06c8a318 (commit)
via 18665064e7946e537931caccde60c180e9796127 (commit)
via 7e0b47d099a071d438c45e7cde177d89a0f3151e (commit)
via c738282e2dabd4004dd451edc1d5dd36b4e59df7 (commit)
via ccf4516112346e4722125b2004138b7a78d701e0 (commit)
via fde6aa002772b6529f185148ce22cdbfabfe6a49 (commit)
via 87a710de67996ae01dcee1c2e05f09c9c392f82d (commit)
via 74c286503e39fe3f70a52a0f8b7b7c206508a494 (commit)
via 7acdd8d445081e1a4c70ed58298367a96ccb55fd (commit)
via c39edd26a68da49eb507b3d842b8929a57f2710b (commit)
via 2f0e203d44adda18249239ff33a75102a8477e82 (commit)
via 46d8b0039515a169b9a0297ff530aea7a25f0a54 (commit)
via 16dea270c9125075b74983e10cf1af7b8851f37c (commit)
via 88fe64ea760b7c2f4e2f5d4fdcc8c298481b46b7 (commit)
via 143264e9d77c446e9f5d9f2611198bb73de82243 (commit)
via f3cd2b49392a4883751e2dbce2d8677e46d1f167 (commit)
via f45931cc8a5c0924d77fb6c6681eeb467dc91302 (commit)
via da3579feea036aa2b7d094b1c260a80a69d2f9aa (commit)
via e7a483b3f53323d03b64f49385d173ceb595c39c (commit)
via 5162bd7739ccfab83a0e81e44ba5e46314eae0cf (commit)
via 6f344e77217625e36e924f41c24d88ac0ba685be (commit)
via f4130b056637b3b14635d8211ad7d720a74ae9c2 (commit)
via 0ae219d8e8bfeacdfe026ad1e3576ab401d79724 (commit)
via 946442d00fdfdc6657b5d068b6aacd859c80383a (commit)
via 2500db669d6463c3ee2f607be63ac0690cf7205e (commit)
via 6994c90205b1c8b7233a47a6ad8b07060ed6b9bd (commit)
via 1a488b6c5ef9970422880a4a6a348d89a66805d7 (commit)
via 45d2c5e07fc8eb3b4a9493470118fe5765164365 (commit)
via 58639475bb07feaf4755b83942a572abb2943743 (commit)
via 9d7bd1274b7ff4578e108c8c13bad81cc2867d14 (commit)
via fededac895f51f0d3aebe33486e0c7303812eb34 (commit)
via c8d00f7c62ecf19879d70e6dcfdc0ab14551b6c9 (commit)
via cfbf803bb49ad944bef4070fec9f2c999a98cb78 (commit)
via a2b82fc102f6a4f987bf9ba59b05a336dfde8ac4 (commit)
via 0b2800af6eedeba3b71108765fd4e04195c0a4ad (commit)
via ea07789d058ca92c5617c2aca304fb8ad8c69c18 (commit)
via 1e1f87c3540b2f9de576a0267c694e8ff19a1db1 (commit)
via 2afcb422fee3bbb9d3c8403f98e489f195184471 (commit)
via 38a875a2d8c9337c1019b11473f989519e16a778 (commit)
via 16ad2c551b5a2e90d0931644a07caf02b254fae7 (commit)
via 29b82a61f1826a72597adcd7ff38e153a135379e (commit)
via dc25eefe48a9c76b9889dde024ce8e88ee1eb363 (commit)
via a41cac4287573ff826bdad6e9ec61b5991c757e1 (commit)
via d5f2a0d0954acd8bc33aabb220fab31652394fcd (commit)
via 7831eb91e8d50e8cdf9f17487c86584efbfe4855 (commit)
via 994dfcd88af7dbc2a8a6221cb662285faab3f6a3 (commit)
via fcca4e8c09253602530fe66a04e8e9b71bbf4739 (commit)
via 76b4abce83b333c61b596df651dfc2f9e3d72100 (commit)
via 592db047a5a82c5af9e837dc2fc9a9df102af8ed (commit)
via e488b9b74d562517bf430b1de30747d575966b07 (commit)
via 72e89527efb75c3aaec0843bf81b08581c3b1f58 (commit)
via b9b2b5e9a5aba9c87939ff116d103c8e9d42c550 (commit)
via 1519438c8f9c74286b84c7258ea9ede4a5a03a7d (commit)
via e817fc7e97fff519a1e6b7a0ef56fd966a274039 (commit)
via e0d3a70defab35a0dd3f1beffc3495b8965883d6 (commit)
via 4ca74b26c112156ec41226073e8e70ea26d1ee0b (commit)
via 8fa04182fc57c74999d148bc14e5b6a6b3929c4c (commit)
via 72fca133ddc102924bef14d4281314309fb51201 (commit)
via 4bc06a779cbe53dd5ad91ef4fe0c0262e430f03e (commit)
via 9204d1f5951ea88ce351297885c83be3ceb33344 (commit)
via c077699ff43ddc2dff552b616044dab306f23666 (commit)
via 2ab76e6181396639a60994b5c78493fa2235b304 (commit)
via e41be59e113c11dbad91634d47c832dffa628e8f (commit)
via c7e380374b96d23f682e81a8ff159c8e39538103 (commit)
via 48bbc35c15b307c83ade8ee4dedbf2217aee3530 (commit)
via a569e3510c94ab28efa0a6a50ec657ae37035d43 (commit)
via 8cb0a56a019c3cbaacc8ccdeebaf3906eeda8a74 (commit)
via 0b690e4c4a93543a1f0e0a2aff0206d605ac1d33 (commit)
via 686768228ffccfa16bc367776cff3eb6dd9f86dc (commit)
via 03ef197742328752227b359e5c10cd12de97a967 (commit)
via 433488548230ab8742f9d7b71fa33bc56484ec86 (commit)
via 8b47d98c888f29facede10aecc8bf4d39d8f21ce (commit)
via 8da51f50f03d1b84d9be0bb73d4091ddf32149fb (commit)
via 52588836afbc4f6d66fd93d30bc37867bfeb2eb8 (commit)
via 02fb830f3e15239a263d109e8bda0256418401bb (commit)
via a5eb53f5b3fcf01e6df58325e11269e62e259197 (commit)
via ee886b45eb503cf048abf8416ce7b37430f6fb25 (commit)
via a79234992101f76634568d1128e51655ab45b42a (commit)
via 7c7bf1d126cddc59ca3ad540e2c2015853509a51 (commit)
via 49ef97d8a1b783a35a81db555eadbea985b84a11 (commit)
via b48e4ef0e4f09fdaa48f11dd97e1c378007cebaf (commit)
via ab579f5245e995718410e2f59bb7a82814f138a0 (commit)
via 943f4ea870d5d00101dcdb4dfb5a6892ed3eb634 (commit)
via ef6d94432720eaf1fb73d65d78915a23aada2a25 (commit)
via 9eab41529f7da65498b241f988a8ca096a4286c8 (commit)
via a2500cf8f53c5beaa5e9f872458f10650f91b95d (commit)
via da4f84b70faf79d279dd1ad978ee007b9fbd6447 (commit)
via adc3e2f94dcb38c4594408774599d946605f3cdb (commit)
via 60c3b4447c47bd08d990d91a3a412ac6b329ff08 (commit)
via dc4cf0c6d071ef5661203ef42894ff8ecc6a90e4 (commit)
via 1a3909427319f08fb5dacef02baa8c89b0b97408 (commit)
via c8aff6addd2137edd3b5b727ff40c3f7f6b00e71 (commit)
via 320f10a5395ec1799f11d172ad6936da5467e815 (commit)
via 323d64cc1465f7a1ef8813fa7a541f495c5e9dba (commit)
via 2b5052e66a62634e8da2495a0d97018a144b7919 (commit)
via 3d234878c77fab6f7bb172c5ef2bd21cde768a72 (commit)
via 935c7cc4033d440f270cb2d62114024e3578a4d3 (commit)
via d83205544b88dc4a9c5584212034c03a4f9e32fc (commit)
via 98a1e1dd654b98ccd453f38618f1ee0ca0c33a2a (commit)
via 1438cd564759a281add9fa7c8ed04280848f6ad4 (commit)
via 7f652b9b12ac550b784db4fd52df91f672ab4d94 (commit)
via 85b0a75a0499f2a7a08d6f597e3f90ee4f7be636 (commit)
via 7185b57ec3dfe44fb5ed0188e6234725cfb8fff0 (commit)
via 1910b46df9c17fd817dd38ccf6748cdc49c6e821 (commit)
via da5d0b9b61f06c13ecfad23fd0dd4b4fc0734c77 (commit)
via 3f5fb3767356584c73a7de584adbc4da6159683a (commit)
via e1ff4dda628311346a699da5adf402d859a517cb (commit)
via 929f85f8deb68ba58610354c8161c0e1c0806006 (commit)
via c7b8894303b7e88629c0b838080bfc6097c05ec8 (commit)
via d35ac0a1d9375ec6e4c01db0d2107ecf177b7ab8 (commit)
via 0f3dd4e12dc74c1c5985e9b701b59fe2355af931 (commit)
via ac01dfd069e66c85409e93b40385a393bb5c5a59 (commit)
via 61e6c39d6f14c8d06956e92002b53f9448453b4d (commit)
via 598d81ecdbc43b8e6869ba65c60b1d7b4fb155e3 (commit)
via cdb6afba87fa03187512682b8348e65dc5e24a0f (commit)
via 5dc80a3df42d7725b56cc33d33249363a7a14279 (commit)
via b9c101fd307c164f7551d00068358828af4ed549 (commit)
via 025659c532f8521d8a36476d2f1a14593c9690b5 (commit)
via 7482587271347cc071f95cf1709a0370c0087fd7 (commit)
via 8c06e54ac7cdefa669326da0280294ea88196575 (commit)
via a154032687e8ba7c5c5cff880f34cd37166688c9 (commit)
via 6620f7a7d1c0d952ad6da4b1441608cf56c2702b (commit)
via c832257c559d982c221595331f75e16df2e0b5f5 (commit)
via 4be4af38b1179f61740628da95a5c0853e1a17ea (commit)
via 79117eccd04c2a99d80026f7c0df14a8f6bccad0 (commit)
via e43764fda36554cd8c8f7a8f6aaec51ddc48014a (commit)
via 969260c87bd0916bfa951f4801bc7e4e60872fe2 (commit)
via 1e39232770b629d8e944f62c5f01bfa6c3b09384 (commit)
via f9028be22e3a0ad08b2f0e51d823bdf868621019 (commit)
via 4df6d1597b68281fee3999542219c18029b168eb (commit)
via 73b25590661745a2e39c6235698ee93bf00ddba3 (commit)
via 298fd19fe3405ccc76d5d7ab8f91d80c53bcf44b (commit)
via b3c562f2bd85fa9f825d59f8601ba99e04e661f3 (commit)
via 18a4180734c1896f12afadd40a2a658f3c5c795f (commit)
via e62bdea81b790e8694075cad0255244226a00e5d (commit)
via 423740919b7a050dc1255a0ebdc6ab6b71ea777f (commit)
via 504f4708e55050fb6f9dfa7f784dbcceb5e1fe16 (commit)
via f0229b977d354a137df49137655eb3ec56d96f97 (commit)
via 2cab68004571463fdaeefe5659380fbbf9c54e01 (commit)
via 807f3b5cd6b5f7daed92413e9ab771ec500b6536 (commit)
via d7fa28172fa3b2481e1f1c1ddec05ff7ae21bce5 (commit)
via 772de981b14ae73731b54e6dcc5b84935584f1ce (commit)
via bd04d06c2d51af45ed818e90ef500936dafca1c4 (commit)
via 44160bfeffd56f5ccd6c8400eb8c3e883ac10b19 (commit)
via 29f6d05f66afabcd46c532622b10b370b65d3090 (commit)
via f941f042dc2c953390354434e9a4563cb67b14e0 (commit)
via 54b12f14d032796a8d6638cdbbf6ba5d63835478 (commit)
via 689aa37ea4cc087ad0affeedb2b4f8de5b29919d (commit)
via f4ca083e427788a22a4979a104b517a5a4ef3df9 (commit)
via 3a6ae66c2a737475ed55fb28cd3cd49b49e7295e (commit)
via 7184eb4185f5bd6577dda90e5100c13e238ed8fe (commit)
via 2f220f9ff1a51348ea225d72a75d58d5d81031aa (commit)
via a87cf3103eb903132064550a8e75715d9b644620 (commit)
via 427bc05eee647c9c5afb013f07c0cc73ff17aecc (commit)
via 4f5e8221042782288e57974e38d4ab7d9de54740 (commit)
via a6949cd6433d089e240c6bdec095e12c4230214d (commit)
via 0857d558e83343a266e8bf67f835017c07f773c0 (commit)
via 03a458983cf7d345ffa7d9d1b2cad0b509b3be54 (commit)
via 9bfdf12f2fd3108a4b3b772c20e9b78396164042 (commit)
via 4daf58b65f126c84a0d68d102ad899e260ea5662 (commit)
via 20a8d4adbdcc32b17fabc43ef1e6af4c4b7c67d2 (commit)
via fc2a9e9876510fd425d693aaa5b93230606d26ec (commit)
via 5306dddc94101bb87bcad59d1c29adc7bef4fe75 (commit)
via b1b78de66931aa416fda81f136b584f515561afd (commit)
via 8aa47f6c343499103fcf86bdbe4c3918253a810a (commit)
via d7eecc60198a4bdd4cdf23dda9663f9d0d803f9b (commit)
via 6713fdc5fb825c90598bbc48029027ed3f2515e0 (commit)
via 42a5a7952572019c06a8dddec9994cdbe54e0d4a (commit)
via bc8cc9cf605cfbe065d0e27e8a081e5f54c6a4cd (commit)
via 69abb657226d2002827b238503ef0f31a81d66c0 (commit)
via a752e1aeacc9470ff3dfb172bf3f9064eefc3161 (commit)
via d42136528ea0edc6c7ea45c3c4d4c542217f9d33 (commit)
via 1024b43c68506682dd55cf1cf9c5fc9dbc8cd96b (commit)
via 31df868ff405b57c6767fad73a7ba82ac0544e4a (commit)
via 51c2df1a2974002d93335c5d762353795ba8ac92 (commit)
via 9c2f41d0ce4250623b36966eb04698d55eac7509 (commit)
via 97e41248dcb4ec3285d1e5f6d0f7d922952c63a6 (commit)
via 8f8bfc92e622717d75684b8bd36181b525a22309 (commit)
via 1d139b0a312bae07267448c6e679b50ce24e5fc2 (commit)
via 6e966c8f89b249a3f1c1a87201f24db656a06720 (commit)
via 882b1c68c77a1e3e8261fd7a8ff9d654313963b2 (commit)
via 4cef89808213a50c9da3aa469d6253ff939f254b (commit)
via 8f4d5d803a3fad8019f8b4b638731d533c6dae16 (commit)
via 4f47c644ff2ba7b3d062e51d4b7eef4f87417301 (commit)
via 3c9911b4fde96eeec42bdf951ac6beb003eb0184 (commit)
via eecd53050bae78469caad312532cbf404244f1e2 (commit)
via cb324f11a8b5ac623b9c4ed491d7ec01551531cf (commit)
via d20b1f2ce6030846d6de80f6addf2c5ce53d564f (commit)
via b9be6a9538f9d11946c5291e7063aaa0db990261 (commit)
via 39aa6489f86c5aa33e07da05c0f0be4ca2d9d9ae (commit)
via 85d28a85b04c5c6a6905c6e018dc9a899afc4494 (commit)
via 80f01bc608cbd779c76a6e5d5ca8dade8b37dbdd (commit)
via 1ca6a3ac00cdf22e6fea1157bc887dd44e1f87a7 (commit)
via 8b0cb0ded8035fcfb9cdd483ab3936db6690b97e (commit)
via fa9feb90e8588508daabccf397cc3fd4751a0e70 (commit)
via ae0620edd4c9cb477066e5f29f1d983b79554f4a (commit)
via 757a5b14ef9101d99c47e7155a1b19b526d09389 (commit)
via c6302d0c4226e13753428df87e7d7864e86ed778 (commit)
via b91e4297ed4ba93adcb9af382f00c301876bf8de (commit)
via f5b9768aba32bb7673458eeeb14f0f56734427bc (commit)
via decee38f00f3efac436fa899bf6160fff787e318 (commit)
via 35b62ac905d85ea7216a2d61b1cfb62a890bdea6 (commit)
via d8595ab6bd85996c00019da0b086fdb40374e1f9 (commit)
via edd0b0623481ea70b4af23a0cbd2bb4ef797d10f (commit)
via 45e29ee0eeeced40dd047ec9da32da674124c55e (commit)
from 7f8feaab9ff4bdfa54c61e4cdcc5e9216fab3c3c (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 b222f7faadc7ed7898a936a2b97b2a385c6a2a1b
Author: Thomas Markwalder <tmark at isc.org>
Date: Sun Jun 2 07:49:04 2013 -0400
[2956] Created the initial, implementation of DHCP-DDNS service controller
class, D2Controller, the base class DControllerBase, and unit tests.
commit 80d16e0dfbaeb8053cbd29d6b0512d75351a2c25
Merge: fd911f4 dbe4772
Author: Thomas Markwalder <tmark at isc.org>
Date: Thu May 30 10:05:54 2013 -0400
Merge branch 'master' into trac2956. 2956 was created before 2955, but
needs 2955 (which is now complete) to finish.
commit fd911f4775865a204a9a67dfcf290d8395e0734d
Author: Thomas Markwalder <tmark at isc.org>
Date: Thu May 30 09:39:48 2013 -0400
[2956] Interrim checkin to allow merge with 2955. Note a subsequent commit
will be required to make d2 build.
Modified files:
Makefile.am
d2_log.cc
d2_log.h
d2_messages.mes
d2.spec
main.cc
tests/Makefile.am
tests/d2_test.py
New files:
d2_controller.cc
d2_controller.h
d_controller.cc
d_controller.h
spec_config.h
tests/d2_controller_unittests.cc
tests/d_controller_unittests.cc
tests/d_test_stubs.cc
tests/d_test_stubs.h
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 70 ++-
configure.ac | 3 +-
doc/design/ipc-high.txt | 4 +-
src/bin/auth/query.cc | 6 +
src/bin/auth/tests/auth_srv_unittest.cc | 32 ++
.../auth/tests/datasrc_clients_builder_unittest.cc | 2 +
src/bin/auth/tests/query_unittest.cc | 107 +++--
src/bin/d2/Makefile.am | 9 +-
src/bin/d2/d2.spec | 24 +-
src/bin/d2/d2_controller.cc | 57 +++
src/bin/d2/d2_controller.h | 64 +++
src/bin/d2/d2_log.cc | 7 +-
src/bin/d2/d2_log.h | 5 +
src/bin/d2/d2_messages.mes | 96 +++-
src/bin/d2/d2_process.cc | 89 ++++
src/bin/d2/d2_process.h | 101 ++++
src/bin/d2/d_controller.cc | 444 +++++++++++++++++
src/bin/d2/d_controller.h | 497 ++++++++++++++++++++
src/bin/d2/d_process.h | 169 +++++++
src/bin/d2/main.cc | 87 +---
src/bin/d2/tests/Makefile.am | 11 +
src/bin/d2/tests/d2_controller_unittests.cc | 215 +++++++++
src/bin/d2/tests/d2_process_unittests.cc | 160 +++++++
src/bin/d2/tests/d2_test.py | 4 +-
src/bin/d2/tests/d_controller_unittests.cc | 368 +++++++++++++++
src/bin/d2/tests/d_test_stubs.cc | 198 ++++++++
src/bin/d2/tests/d_test_stubs.h | 422 +++++++++++++++++
src/bin/dhcp4/dhcp4_srv.cc | 118 +++--
src/bin/dhcp4/dhcp4_srv.h | 32 +-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 419 +++++++++++++----
src/bin/dhcp6/dhcp6_srv.cc | 7 +-
src/bin/usermgr/b10-cmdctl-usermgr.py.in | 2 +-
src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py | 21 +-
src/bin/xfrout/tests/xfrout_test.py.in | 60 +++
src/bin/xfrout/xfrout.py.in | 24 +-
src/lib/asiodns/asiodns_messages.mes | 6 +
src/lib/asiodns/dns_service.cc | 5 +-
src/lib/asiodns/sync_udp_server.cc | 27 +-
src/lib/asiodns/sync_udp_server.h | 50 +-
src/lib/asiodns/tcp_server.cc | 36 +-
src/lib/asiodns/tests/dns_server_unittest.cc | 107 +++--
src/lib/asiolink/Makefile.am | 1 +
src/lib/asiolink/local_socket.cc | 102 ++++
src/lib/asiolink/local_socket.h | 132 ++++++
src/lib/asiolink/tests/Makefile.am | 1 +
src/lib/asiolink/tests/local_socket_unittest.cc | 253 ++++++++++
src/lib/cc/proto_defs.cc | 3 +
src/lib/config/ccsession.cc | 16 +
src/lib/config/ccsession.h | 25 +
src/lib/config/tests/ccsession_unittests.cc | 40 ++
src/lib/datasrc/client.h | 20 +-
src/lib/datasrc/client_list.cc | 34 +-
src/lib/datasrc/client_list.h | 17 +-
src/lib/datasrc/datasrc_messages.mes | 5 +-
src/lib/datasrc/exceptions.h | 11 +
src/lib/datasrc/memory/Makefile.am | 1 +
src/lib/datasrc/memory/domaintree.h | 23 +-
src/lib/datasrc/memory/memory_client.cc | 11 +-
src/lib/datasrc/memory/memory_messages.mes | 6 +
src/lib/datasrc/memory/rdataset.h | 9 +
.../datasrc/memory/segment_object_holder.cc} | 35 +-
src/lib/datasrc/memory/segment_object_holder.h | 82 +++-
src/lib/datasrc/memory/zone_data.cc | 19 +-
src/lib/datasrc/memory/zone_data.h | 63 ++-
src/lib/datasrc/memory/zone_data_loader.cc | 70 +--
src/lib/datasrc/memory/zone_data_loader.h | 2 +-
src/lib/datasrc/memory/zone_data_updater.cc | 74 ++-
src/lib/datasrc/memory/zone_data_updater.h | 43 +-
src/lib/datasrc/memory/zone_table.cc | 76 ++-
src/lib/datasrc/memory/zone_table.h | 91 +++-
src/lib/datasrc/memory/zone_writer.cc | 67 ++-
src/lib/datasrc/memory/zone_writer.h | 40 +-
src/lib/datasrc/result.h | 25 +-
src/lib/datasrc/tests/client_list_unittest.cc | 37 +-
src/lib/datasrc/tests/memory/Makefile.am | 8 +
.../datasrc/tests/memory/memory_client_unittest.cc | 38 ++
.../tests/memory/rdata_serialization_unittest.cc | 2 +-
src/lib/datasrc/tests/memory/rdataset_unittest.cc | 74 ++-
.../tests/memory/rrset_collection_unittest.cc | 18 +-
.../tests/memory/segment_object_holder_unittest.cc | 78 ++-
.../tests/memory/zone_data_loader_unittest.cc | 42 ++
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 10 +
.../tests/memory/zone_data_updater_unittest.cc | 205 ++++++--
.../datasrc/tests/memory/zone_finder_unittest.cc | 56 +--
src/lib/datasrc/tests/memory/zone_loader_util.cc | 7 +-
src/lib/datasrc/tests/memory/zone_loader_util.h | 7 +-
.../datasrc/tests/memory/zone_table_unittest.cc | 86 +++-
.../datasrc/tests/memory/zone_writer_unittest.cc | 64 ++-
.../datasrc/tests/zone_finder_context_unittest.cc | 2 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 5 +-
src/lib/dhcp/Makefile.am | 6 +
src/lib/dhcp/hwaddr.h | 6 +-
src/lib/dhcp/iface_mgr.cc | 87 +++-
src/lib/dhcp/iface_mgr.h | 154 ++++--
src/lib/dhcp/iface_mgr_bsd.cc | 14 +-
src/lib/dhcp/iface_mgr_linux.cc | 17 +-
src/lib/dhcp/iface_mgr_sun.cc | 13 +-
src/lib/dhcp/option.cc | 13 +-
src/lib/dhcp/option.h | 11 +-
src/lib/dhcp/option_custom.cc | 38 +-
src/lib/dhcp/option_custom.h | 9 +-
src/lib/dhcp/option_definition.cc | 6 +-
src/lib/dhcp/option_string.cc | 86 ++++
src/lib/dhcp/option_string.h | 114 +++++
src/lib/dhcp/pkt4.cc | 50 +-
src/lib/dhcp/pkt4.h | 100 +++-
src/lib/dhcp/pkt_filter.h | 29 +-
src/lib/dhcp/pkt_filter_inet.cc | 22 +-
src/lib/dhcp/pkt_filter_inet.h | 16 +-
src/lib/dhcp/pkt_filter_lpf.cc | 243 +++++++++-
src/lib/dhcp/pkt_filter_lpf.h | 15 +-
src/lib/dhcp/protocol_util.cc | 243 ++++++++++
src/lib/dhcp/protocol_util.h | 153 ++++++
src/lib/dhcp/std_option_defs.h | 2 +-
src/lib/dhcp/tests/Makefile.am | 8 +
src/lib/dhcp/tests/iface_mgr_unittest.cc | 251 ++++++++--
src/lib/dhcp/tests/libdhcp++_unittest.cc | 62 +--
src/lib/dhcp/tests/option_custom_unittest.cc | 4 +-
src/lib/dhcp/tests/option_definition_unittest.cc | 10 +-
src/lib/dhcp/tests/option_string_unittest.cc | 160 +++++++
src/lib/dhcp/tests/pkt4_unittest.cc | 72 ++-
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc | 269 +++++++++++
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc | 293 ++++++++++++
src/lib/dhcp/tests/protocol_util_unittest.cc | 390 +++++++++++++++
src/lib/dhcpsrv/database_backends.dox | 45 +-
src/lib/dns/gen-rdatacode.py.in | 52 +-
src/lib/dns/rdata/any_255/tsig_250.cc | 245 +++++++---
src/lib/dns/rdata/any_255/tsig_250.h | 14 +-
src/lib/dns/rdata/ch_3/a_1.cc | 8 +-
src/lib/dns/rdata/generic/minfo_14.cc | 73 ++-
src/lib/dns/rdata/generic/minfo_14.h | 6 +-
src/lib/dns/rdata/generic/rp_17.cc | 86 +++-
src/lib/dns/rdata/generic/rp_17.h | 12 +-
src/lib/dns/rdata/generic/sshfp_44.cc | 239 +++++++---
src/lib/dns/rdata/generic/sshfp_44.h | 17 +-
src/lib/dns/rdata/hs_4/a_1.cc | 8 +-
src/lib/dns/rrparamregistry-placeholder.cc | 44 +-
src/lib/dns/rrparamregistry.h | 17 +-
src/lib/dns/tests/rdata_minfo_unittest.cc | 113 +++--
src/lib/dns/tests/rdata_rp_unittest.cc | 72 ++-
src/lib/dns/tests/rdata_sshfp_unittest.cc | 97 +++-
src/lib/dns/tests/rdata_tsig_unittest.cc | 198 +++++---
src/lib/dns/tests/rrparamregistry_unittest.cc | 7 +-
src/lib/python/isc/config/ccsession.py | 36 ++
src/lib/python/isc/config/tests/ccsession_test.py | 26 +
src/lib/util/memory_segment_mapped.cc | 30 +-
src/lib/util/memory_segment_mapped.h | 9 +-
.../util/tests/memory_segment_mapped_unittest.cc | 5 +-
tests/Makefile.am | 2 +-
tests/lettuce/Makefile.am | 1 +
tests/lettuce/README | 7 +-
...ransfer_master.conf.orig => xfrout_master.conf} | 10 +-
tests/lettuce/features/auth_badzone.feature | 8 +-
tests/lettuce/features/terrain/loadzone.py | 86 ++++
tests/lettuce/features/terrain/terrain.py | 4 +-
tests/lettuce/features/terrain/transfer.py | 71 ++-
tests/lettuce/features/xfrout_bind10.feature | 39 ++
.../logger.py => tests/lettuce/run_python-tool.sh | 17 +-
tests/lettuce/setup_intree_bind10.sh.in | 2 +-
tests/lettuce/tools/xfr-client.py | 103 ++++
160 files changed, 9612 insertions(+), 1464 deletions(-)
create mode 100644 src/bin/d2/d2_controller.cc
create mode 100644 src/bin/d2/d2_controller.h
create mode 100644 src/bin/d2/d2_process.cc
create mode 100644 src/bin/d2/d2_process.h
create mode 100644 src/bin/d2/d_controller.cc
create mode 100644 src/bin/d2/d_controller.h
create mode 100644 src/bin/d2/d_process.h
create mode 100644 src/bin/d2/tests/d2_controller_unittests.cc
create mode 100644 src/bin/d2/tests/d2_process_unittests.cc
create mode 100644 src/bin/d2/tests/d_controller_unittests.cc
create mode 100644 src/bin/d2/tests/d_test_stubs.cc
create mode 100644 src/bin/d2/tests/d_test_stubs.h
create mode 100644 src/lib/asiolink/local_socket.cc
create mode 100644 src/lib/asiolink/local_socket.h
create mode 100644 src/lib/asiolink/tests/local_socket_unittest.cc
copy src/{bin/resolver/bench/dummy_work.h => lib/datasrc/memory/segment_object_holder.cc} (63%)
create mode 100644 src/lib/dhcp/option_string.cc
create mode 100644 src/lib/dhcp/option_string.h
create mode 100644 src/lib/dhcp/protocol_util.cc
create mode 100644 src/lib/dhcp/protocol_util.h
create mode 100644 src/lib/dhcp/tests/option_string_unittest.cc
create mode 100644 src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
create mode 100644 src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
create mode 100644 src/lib/dhcp/tests/protocol_util_unittest.cc
create mode 100644 tests/lettuce/Makefile.am
copy tests/lettuce/configurations/{xfrin/retransfer_master.conf.orig => xfrout_master.conf} (81%)
create mode 100644 tests/lettuce/features/terrain/loadzone.py
create mode 100644 tests/lettuce/features/xfrout_bind10.feature
copy src/lib/python/isc/cc/logger.py => tests/lettuce/run_python-tool.sh (72%)
mode change 100644 => 100755
mode change 100644 => 100755 tests/lettuce/setup_intree_bind10.sh.in
create mode 100755 tests/lettuce/tools/xfr-client.py
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 1443d13..7347cf9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,67 @@
+621. [func] team
+ libdns++: All Rdata classes now use the generic lexer in
+ constructors from text. This means that the name fields in such
+ RRs in a zone file can now be non-absolute (the origin name in that
+ context will be used), e.g., when loaded by b10-loadzone. Note
+ that the existing string constructors for these Rdata classes also
+ use the generic lexer, and they now expect an absolute name (with
+ the trailing '.') in the name fields.
+ (Trac #2522, git ea97070cf6b41299351fc29af66fa39c6465d56a)
+ (Trac #2521, git c6603decaadcd33ccf9aee4a7b22447acec4b7f6)
+ (See also ChangeLog 594, 564, 545)
+
+620. [bug] jinmei
+ b10-auth now returns SERVFAIL to queries for a zone that is
+ configured to be loaded in-memory but isn't due to load time
+ errors (missing zone file or errors in the zone file, etc).
+ Such zones were previously treated as non existent and would
+ result in REFUSED or unintentional match against less specific
+ zones. The revised behavior is also compatible with BIND 9.
+ (Trac #2905, git 56ee9810fdfb5f86bd6948e6bf26545ac714edd8)
+
+619. [bug] jinmei
+ b10-xfrout now uses blocking send for xfr response messages
+ to prevent abrupt termination of the stream due to a slower
+ client or narrower network bandwidth.
+ (Trac #2934, git bde0e94518469557c8b455ccbecc079a38382afd)
+
+618. [func]* marcin
+ b10-dhcp4: Added the ability for the server to respond to a
+ directly connected client which does not yet have an IP address.
+ On Linux, the server will unicast the response to the client's
+ hardware address and the 'yiaddr' (the client's new IP
+ address). Sending a response to the unicast address prevents other
+ (not interested) hosts from receiving the server response. This
+ capability is not yet implemented on non-Linux Operating Systems
+ where, in all cases, the server responds to the broadcast
+ address. The logic conforms to section 4.1 of RFC 2131.
+ (Trac #2902, git c2d40e3d425f1e51647be6a717c4a97d7ca3c29c)
+
+617. [bug] marcin
+ b10-dhcp4: Fixed a bug whereby the domain-name option was encoded
+ as FQDN (using technique described in RFC1035) instead of a string.
+ Also, created new class which represents an option carrying a single
+ string value. This class is now used for all standard options of
+ this kind.
+ (Trac #2786, git 96b1a7eb31b16bf9b270ad3d82873c0bd86a3530)
+
+616. [doc] stephen
+ Added description to the DHCP "Database Back-Ends" section of the
+ BIND 10 Developer's Guide about how to set up a MySQL database for
+ testing the DHCP MySQL backend.
+ (Trac #2653, git da3579feea036aa2b7d094b1c260a80a69d2f9aa)
+
+615. [bug] jinmei
+ b10-auth: Avoid referencing to a freed object when authoritative
+ server addresses are reconfigured. It caused a crash on a busy
+ server during initial startup time, and the same crash could also
+ happen if listen_on parameters are reconfigured at run time.
+ (Trac #2946, git d5f2a0d0954acd8bc33aabb220fab31652394fcd)
+
614. [func] tmark
- b10-d2: Initial DHCP-DDNS (a.k.a. D2) module implemented. Currently it does
- nothing useful, except for providing the skeleton implementation
- to be expanded in the future.
+ b10-d2: Initial DHCP-DDNS (a.k.a. D2) module implemented.
+ Currently it does nothing useful, except for providing the
+ skeleton implementation to be expanded in the future.
(Trac #2954, git 392c5ec5d15cd8c809bc9c6096b9f2bfe7b8c66a)
613. [func] jinmei
@@ -25,7 +85,7 @@
while Xfrin is running.
(Trac #2252, git e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413)
-bind10-1.0.0beta2 released on May 10, 2013
+bind10-1.1.0beta2 released on May 10, 2013
610. [bug] muks
When the sqlite3 program is not available on the system (in
@@ -128,7 +188,7 @@ bind10-1.0.0beta2 released on May 10, 2013
messages.
(Trac #2827, git 29c3f7f4e82d7e85f0f5fb692345fd55092796b4)
-bind10-1.0.0beta1 released on April 4, 2013
+bind10-1.1.0beta1 released on April 4, 2013
598. [func]* jinmei
The separate "static" data source is now deprecated as it can be
diff --git a/configure.ac b/configure.ac
index af9a602..fc216c4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10, 20130510, bind10-dev at isc.org)
+AC_INIT(bind10, 20130529, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
# serial-tests is not available in automake version before 1.13. In
# automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
@@ -1332,6 +1332,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/tests/testdata/Makefile
+ tests/lettuce/Makefile
m4macros/Makefile
dns++.pc
])
diff --git a/doc/design/ipc-high.txt b/doc/design/ipc-high.txt
index 3f46b5c..5addb88 100644
--- a/doc/design/ipc-high.txt
+++ b/doc/design/ipc-high.txt
@@ -210,7 +210,7 @@ about changes to zone data, they'd subscribe to the
`Notifications/ZoneUpdates` group. Then, other client (let's say
`XfrIn`, with session ID `s12345`) would send something like:
- s12345 -> Notifications/ZoneUpdates
+ s12345 -> notifications/ZoneUpdates
{"notification": ["zone-update", {
"class": "IN",
"origin": "example.org.",
@@ -221,7 +221,7 @@ Both receivers would receive the message and know that the
`example.org` zone is now at version 123456. Note that multiple users
may produce the same kind of notification. Also, single group may be
used to send multiple notification names (but they should be related;
-in our example, the `Notifications/ZoneUpdates` could be used for
+in our example, the `notifications/ZoneUpdates` could be used for
`zone-update`, `zone-available` and `zone-unavailable` notifications
for change in zone data, configuration of new zone in the system and
removal of a zone from configuration).
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 770f624..65e5410 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -386,6 +386,12 @@ Query::process(datasrc::ClientList& client_list,
response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
response_->setRcode(Rcode::REFUSED());
return;
+ } else if (!result.finder_) {
+ // We found a matching zone in a data source but its data are not
+ // available.
+ response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
+ response_->setRcode(Rcode::SERVFAIL());
+ return;
}
ZoneFinder& zfinder = *result.finder_;
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index d63dc63..f17d91e 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -1210,6 +1210,38 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
+TEST_F(AuthSrvTest, emptyZone) {
+ // Similar to the previous setup, but the configuration has an error
+ // (zone file doesn't exist) and the query should result in SERVFAIL.
+ // Here we check the rcode other header parameters, and statistics.
+
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {\"example.com\": \"nosuchfile.zone\"},"
+ " \"cache-enable\": true"
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+ createDataFromFile("examplequery_fromWire.wire");
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+ checkAllRcodeCountersZeroExcept(Rcode::SERVFAIL(), 1);
+ ConstElementPtr stats = server.getStatistics()->get("zones")->
+ get("_SERVER_");
+ std::map<std::string, int> expect;
+ expect["request.v4"] = 1;
+ expect["request.udp"] = 1;
+ expect["opcode.query"] = 1;
+ expect["responses"] = 1;
+ expect["qrynoauthans"] = 1;
+ expect["rcode.servfail"] = 1;
+ checkStatisticsCounters(stats, expect);
+}
+
TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
// In this example, we do simple check that query is handled from the
// query handler class, and confirm it returns no error and a non empty
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index 5a24b30..8aa223b 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <util/unittests/check_valgrind.h>
#include <dns/name.h>
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 9bf1358..32092b0 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -133,6 +133,12 @@ const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
+// Name of an "empty" zone: used to simulate the case of
+// configured-but-available zone (due to load errors, etc).
+// Each tested data source client is expected to have this zone (SQLite3
+// currently doesn't have this concept so it's skipped)
+const char* const EMPTY_ZONE_NAME = "empty.example.org";
+
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -799,11 +805,14 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
return (boost::shared_ptr<ClientList>(new SingletonList(client)));
case INMEMORY:
list.reset(new ConfigurableClientList(RRClass::IN()));
+ // Configure one normal zone and one "empty" zone.
list->configure(isc::data::Element::fromJSON(
"[{\"type\": \"MasterFiles\","
" \"cache-enable\": true, "
" \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone\",") +
+ + "\"" + EMPTY_ZONE_NAME + "\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/nosuchfile.zone") +
"\"}}]"), true);
return (list);
case SQLITE3:
@@ -834,39 +843,38 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
class MockClient : public DataSourceClient {
public:
virtual FindResult findZone(const isc::dns::Name& origin) const {
- const Name r_origin(origin.reverse());
- std::map<Name, ZoneFinderPtr>::const_iterator it =
- zone_finders_.lower_bound(r_origin);
-
- if (it != zone_finders_.end()) {
- const NameComparisonResult result =
- origin.compare((it->first).reverse());
- if (result.getRelation() == NameComparisonResult::EQUAL) {
- return (FindResult(result::SUCCESS, it->second));
- } else if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
- return (FindResult(result::PARTIALMATCH, it->second));
- }
- }
+ // Identify the next (strictly) larger name than the given 'origin' in
+ // the map. Its predecessor (if any) is the longest matching name
+ // if it's either an exact match or a super domain; otherwise there's
+ // no match in the map. See also datasrc/tests/mock_client.cc.
- // If it is at the beginning of the map, then the name was not
- // found (we have already handled the element the iterator
- // points to).
- if (it == zone_finders_.begin()) {
+ // Eliminate the case of empty map to simply the rest of the code
+ if (zone_finders_.empty()) {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
- // Check if the previous element is a partial match.
- --it;
- const NameComparisonResult result =
- origin.compare((it->first).reverse());
- if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
- return (FindResult(result::PARTIALMATCH, it->second));
+ std::map<Name, ZoneFinderPtr>::const_iterator it =
+ zone_finders_.upper_bound(origin);
+ if (it == zone_finders_.begin()) { // no predecessor
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ --it; // get the predecessor
+ const result::ResultFlags flags =
+ it->second ? result::FLAGS_DEFAULT : result::ZONE_EMPTY;
+ const NameComparisonResult compar(it->first.compare(origin));
+ switch (compar.getRelation()) {
+ case NameComparisonResult::EQUAL:
+ return (FindResult(result::SUCCESS, it->second, flags));
+ case NameComparisonResult::SUPERDOMAIN:
+ return (FindResult(result::PARTIALMATCH, it->second, flags));
+ default:
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
}
- virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const {
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const
+ {
isc_throw(isc::NotImplemented,
"Updater isn't supported in the MockClient");
}
@@ -878,18 +886,21 @@ public:
}
result::Result addZone(ZoneFinderPtr finder) {
- // Use the reverse of the name as the key, so we can quickly
- // find partial matches in the map.
- zone_finders_[finder->getOrigin().reverse()] = finder;
+ zone_finders_[finder->getOrigin()] = finder;
+ return (result::SUCCESS);
+ }
+
+ // "configure" a zone with no data. This will cause the ZONE_EMPTY flag
+ // on in finZone().
+ result::Result addEmptyZone(const Name& zone_name) {
+ zone_finders_[zone_name] = ZoneFinderPtr();
return (result::SUCCESS);
}
private:
// Note that because we no longer have the old RBTree, and the new
// in-memory DomainTree is not useful as it returns const nodes, we
- // use a std::map instead. In this map, the key is a name stored in
- // reverse order of labels to aid in finding partial matches
- // quickly.
+ // use a std::map instead.
std::map<Name, ZoneFinderPtr> zone_finders_;
};
@@ -916,9 +927,10 @@ protected:
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- // create and add a matching zone.
+ // create and add a matching zone. One is a "broken, empty" zone.
mock_finder = new MockZoneFinder();
mock_client.addZone(ZoneFinderPtr(mock_finder));
+ mock_client.addEmptyZone(Name(EMPTY_ZONE_NAME));
}
virtual void SetUp() {
@@ -949,6 +961,12 @@ protected:
setNSEC3HashCreator(NULL);
}
+ bool isEmptyZoneSupported() const {
+ // Not all data sources support the concept of empty zones.
+ // Specifically for this test, SQLite3-based data source doesn't.
+ return (GetParam() != SQLITE3);
+ }
+
void enableNSEC3(const vector<string>& rrsets_to_add) {
boost::shared_ptr<ConfigurableClientList> new_list;
switch (GetParam()) {
@@ -1144,11 +1162,29 @@ TEST_P(QueryTest, noZone) {
// REFUSED.
MockClient empty_mock_client;
SingletonList empty_list(empty_mock_client);
- EXPECT_NO_THROW(query.process(empty_list, qname, qtype,
- response));
+ EXPECT_NO_THROW(query.process(empty_list, qname, qtype, response));
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
+TEST_P(QueryTest, emptyZone) {
+ // Query for an "empty (broken)" zone. If the concept is supported by
+ // the underlying data source, the result should be SERVFAIL; otherwise
+ // it would be handled as a nonexistent zone, resulting in REFUSED.
+ const Rcode expected_rcode =
+ isEmptyZoneSupported() ? Rcode::SERVFAIL() : Rcode::REFUSED();
+
+ query.process(*list_, Name(EMPTY_ZONE_NAME), qtype, response);
+ responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+
+ // Same for the partial match case
+ response.clear(isc::dns::Message::RENDER);
+ response.setRcode(Rcode::NOERROR());
+ response.setOpcode(Opcode::QUERY());
+ query.process(*list_, Name(string("www.") + EMPTY_ZONE_NAME), qtype,
+ response);
+ responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+}
+
TEST_P(QueryTest, exactMatch) {
EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
@@ -1400,7 +1436,6 @@ TEST_F(QueryTestForMockOnly, badSecureDelegation) {
qtype, response));
}
-
TEST_P(QueryTest, nxdomain) {
EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 445b21e..c18e4dd 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -16,7 +16,7 @@ endif
pkglibexecdir = $(libexecdir)/@PACKAGE@
-CLEANFILES = *.gcno *.gcda spec_config.h d2_srv_messages.h d2_srv_messages.cc
+CLEANFILES = *.gcno *.gcda spec_config.h d2_messages.h d2_messages.cc
man_MANS = b10-d2.8
DISTCLEANFILES = $(man_MANS)
@@ -48,12 +48,19 @@ pkglibexec_PROGRAMS = b10-d2
b10_d2_SOURCES = main.cc
b10_d2_SOURCES += d2_log.cc d2_log.h
+b10_d2_SOURCES += d_process.h
+b10_d2_SOURCES += d2_process.cc d2_process.h
+b10_d2_SOURCES += d_controller.cc d_controller.h
+b10_d2_SOURCES += d2_controller.cc d2_controller.h
nodist_b10_d2_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
b10_d2_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
b10_d2_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_d2_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_d2_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_d2_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_d2dir = $(pkgdatadir)
b10_d2_DATA = d2.spec
diff --git a/src/bin/d2/d2.spec b/src/bin/d2/d2.spec
index 63afb7a..14b1e11 100644
--- a/src/bin/d2/d2.spec
+++ b/src/bin/d2/d2.spec
@@ -1,21 +1,21 @@
{
"module_spec": {
"module_name": "D2",
- "module_description": "DHCP-DDNS process",
+ "module_description": "DHPC-DDNS Service",
"config_data": [
],
"commands": [
- {
- "command_name": "shutdown",
- "command_description": "Shuts down the D2 process.",
- "command_args": [
- {
- "item_name": "pid",
- "item_type": "integer",
- "item_optional": true
- }
- ]
- }
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down the stats httpd",
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
]
}
}
diff --git a/src/bin/d2/d2_controller.cc b/src/bin/d2/d2_controller.cc
new file mode 100644
index 0000000..07a9537
--- /dev/null
+++ b/src/bin/d2/d2_controller.cc
@@ -0,0 +1,57 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/spec_config.h>
+
+namespace isc {
+namespace d2 {
+
+DControllerBasePtr&
+D2Controller::instance() {
+ // If the instance hasn't been created yet, create it. Note this method
+ // must use the base class singleton instance methods. The base class
+ // must own the singleton in order to use it within BIND10 static function
+ // callbacks.
+ if (!getController()) {
+ setController(new D2Controller());
+ }
+
+ return (getController());
+}
+
+DProcessBase* D2Controller::createProcess() {
+ // Instantiate and return an instance of the D2 application process. Note
+ // that the process is passed the controller's io_service.
+ return (new D2Process(getName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+ : DControllerBase(D2_MODULE_NAME) {
+ // set the BIND10 spec file either from the environment or
+ // use the production value.
+ if (getenv("B10_FROM_BUILD")) {
+ setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/d2/d2.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+D2Controller::~D2Controller() {
+}
+
+}; // end namespace isc::d2
+}; // end namespace isc
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
new file mode 100644
index 0000000..46a24ef
--- /dev/null
+++ b/src/bin/d2/d2_controller.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_CONTROLLER_H
+#define D2_CONTROLLER_H
+
+#include <d2/d_controller.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Process Controller for D2 Process
+/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
+/// creates and manages an instance of the DHCP-DDNS application process,
+/// D2Process.
+/// @TODO Currently, this class provides only the minimum required specialized
+/// behavior to run the DHCP-DDNS service. It may very well expand as the
+/// service implementation evolves. Some thought was given to making
+/// DControllerBase a templated class but the labor savings versus the
+/// potential number of virtual methods which may be overridden didn't seem
+/// worth the clutter at this point.
+class D2Controller : public DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns the pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Destructor.
+ virtual ~D2Controller();
+
+private:
+ /// @brief Creates an instance of the DHCP-DDNS specific application
+ /// process. This method is invoked during the process initialization
+ /// step of the controller launch.
+ ///
+ /// @return returns a DProcessBase* to the application process created.
+ /// Note the caller is responsible for destructing the process. This
+ /// is handled by the base class, which wraps this pointer with a smart
+ /// pointer.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Constructor is declared private to maintain the integrity of
+ /// the singleton instance.
+ D2Controller();
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_log.cc b/src/bin/d2/d2_log.cc
index 37289e1..40812e3 100644
--- a/src/bin/d2/d2_log.cc
+++ b/src/bin/d2/d2_log.cc
@@ -19,7 +19,12 @@
namespace isc {
namespace d2 {
-isc::log::Logger d2_logger("d2");
+/// @brief Defines the service name which is used in the controller constructor
+/// and ultimately defines the BIND10 module name.
+const char* const D2_MODULE_NAME = "b10-d2";
+
+/// @brief Defines the logger used within D2.
+isc::log::Logger d2_logger(D2_MODULE_NAME);
} // namespace d2
} // namespace isc
diff --git a/src/bin/d2/d2_log.h b/src/bin/d2/d2_log.h
index 2ddfe5c..bb95a2b 100644
--- a/src/bin/d2/d2_log.h
+++ b/src/bin/d2/d2_log.h
@@ -22,12 +22,17 @@
namespace isc {
namespace d2 {
+/// @brief Defines the executable name, ultimately this is the BIND10 module
+/// name.
+extern const char* const D2_MODULE_NAME;
+
/// Define the logger for the "d2" module part of b10-d2. We could define
/// a logger in each file, but we would want to define a common name to avoid
/// spelling mistakes, so it is just one small step from there to define a
/// module-common logger.
extern isc::log::Logger d2_logger;
+
} // namespace d2
} // namespace isc
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index 08afb9c..86d8996 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -14,15 +14,93 @@
$NAMESPACE isc::d2
-% D2_STARTING : process starting
-This is a debug message issued during a D2 process startup.
+% D2CTL_STARTING DHCP-DDNS controller starting, pid: %1
+This is an informational message issued when controller for DHCP-DDNS
+service first starts.
-% D2_START_INFO pid: %1, verbose: %2, standalone: %3
-This is a debug message issued during the D2 process startup.
-It lists some information about the parameters with which the
-process is running.
+% D2CTL_STOPPING DHCP-DDNS controller is exiting
+This is an informational message issued when the controller is exiting
+following a shut down (normal or otherwise) of the DDHCP-DDNS process.
-% D2_SHUTDOWN : process is performing a normal shutting down
-This is a debug message issued when a D2 process shuts down
-normally in response to command to stop.
+% D2PRC_SHUTDOWN DHCP-DDNS process is performing a normal shut down
+This is a debug message issued when the service process has been instructed
+to shut down by the controller.
+% D2PRC_PROCESS_INIT DHCP-DDNS application init invoked
+This is a debug message issued when the D2 process enters it's
+init method.
+
+% D2PRC_RUN_ENTER process has entered the event loop
+This is a debug message issued when the D2 process enters it's
+run method.
+
+% D2PRC_RUN_EXIT process is exiting the event loop
+This is a debug message issued when the D2 process exits the
+in event loop.
+
+% D2PRC_FAILED process experienced a fatal error: %1
+This is a debug message issued when the D2 process encounters an
+unrecoverable error from within the event loop.
+
+% D2PRC_CONFIGURE new configuration received: %1
+This is a debug message issued when the D2 process configure method
+has been invoked.
+
+% D2PRC_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the D2 process command method
+has been invoked.
+
+% D2CTL_INIT_PROCESS initializing application proces
+This debug message is issued just before the controller attempts
+to create and initialize it's process instance.
+
+% D2CTL_SESSION_FAIL failed to establish BIND 10 session: %1
+The controller has failed to establish communication with the rest of BIND
+10 and will exit.
+
+% D2CTL_DISCONNECT_FAIL failed to disconnect from BIND 10 session: %1
+The controller has failed to terminate communication with the rest of BIND
+10.
+
+% D2CTL_STANDALONE skipping message queue, running standalone
+This is a debug message indicating that the controller is running in the
+process in standalone mode. This means it will not connected to the BIND10
+message queue. Standalone mode is only useful during program development,
+and should not be used in a production environment.
+
+% D2CTL_RUN_PROCESS starting application proces event loop
+This debug message is issued just before the controller invokes
+the application process run method.
+
+% D2CTL_FAILED process failed: %1
+The controller has encountered a fatal error and is terminating.
+The reason for the failure is included in the message.
+
+% D2CTL_CCSESSION_STARTING starting control channel session, specfile: %1
+This debug message is issued just before the controller attempts
+to establish a session with the BIND 10 control channel.
+
+% D2CTL_CCSESSION_ENDING ending control channel session
+This debug message is issued just before the controller attempts
+to disconnect from its session with the BIND 10 control channel.
+
+% D2CTL_CONFIG_STUB configuration stub handler called
+This debug message is issued when the dummy handler for configuration
+events is called. This only happens during intial startup.
+
+% D2CTL_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial process
+configuration has failed. The service will start, but will not
+process requests until the configuration has been corrected.
+
+% D2CTL_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 controller.
+
+% D2CTL_NOT_RUNNING The application process instance is not running
+A warning message is issued when an attempt is made to shut down the
+the process when it is not running.
+
+% D2CTL_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the controller has received an
+updated configuration from the BIND 10 configuration system.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
new file mode 100644
index 0000000..be28283
--- /dev/null
+++ b/src/bin/d2/d2_process.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d2_process.h>
+
+using namespace asio;
+
+namespace isc {
+namespace d2 {
+
+D2Process::D2Process(const char* name, IOServicePtr io_service)
+ : DProcessBase(name, io_service) {
+};
+
+void
+D2Process::init() {
+};
+
+void
+D2Process::run() {
+ // Until shut down or an fatal error occurs, wait for and
+ // execute a single callback. This is a preliminary implementation
+ // that is likely to evolve as development progresses.
+ // To use run(), the "managing" layer must issue an io_service::stop
+ // or the call to run will continue to block, and shutdown will not
+ // occur.
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_ENTER);
+ IOServicePtr& io_service = getIoService();
+ while (!shouldShutdown()) {
+ try {
+ io_service->run_one();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, D2PRC_FAILED).arg(ex.what());
+ isc_throw (DProcessBaseError,
+ std::string("Process run method failed:") + ex.what());
+ }
+ }
+
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_EXIT);
+};
+
+void
+D2Process::shutdown() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_SHUTDOWN);
+ setShutdownFlag(true);
+}
+
+isc::data::ConstElementPtr
+D2Process::configure(isc::data::ConstElementPtr config_set) {
+ // @TODO This is the initial implementation which simply accepts
+ // any content in config_set as valid. This is sufficient to
+ // allow participation as a BIND10 module, while D2 configuration support
+ // is being developed.
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_CONFIGURE).arg(config_set->str());
+
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+D2Process::command(const std::string& command, isc::data::ConstElementPtr args){
+ // @TODO This is the initial implementation. If and when D2 is extended
+ // to support its own commands, this implementation must change. Otherwise
+ // it should reject all commands as it does now.
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_COMMAND).arg(command).arg(args->str());
+
+ return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command:"
+ + command));
+}
+
+D2Process::~D2Process() {
+};
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
new file mode 100644
index 0000000..4ddf8be
--- /dev/null
+++ b/src/bin/d2/d2_process.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_PROCESS_H
+#define D2_PROCESS_H
+
+#include <d2/d_process.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief DHCP-DDNS Application Process
+///
+/// D2Process provides the top level application logic for DHCP-driven DDNS
+/// update processing. It provides the asynchronous event processing required
+/// to receive DNS mapping change requests and carry them out.
+/// It implements the DProcessBase interface, which structures it such that it
+/// is a managed "application", controlled by a management layer.
+
+class D2Process : public DProcessBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ D2Process(const char* name, IOServicePtr io_service);
+
+ /// @brief Will be used after instantiation to perform initialization
+ /// unique to D2. @TODO This will likely include interactions with
+ /// QueueMgr and UpdateMgr, to prepare for request receipt and processing.
+ /// Current implementation successfully does nothing.
+ /// @throw throws a DProcessBaseError if the initialization fails.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ /// The initial implementation is quite basic, surrounding calls to
+ /// io_service->runOne() with a test of the shutdown flag.
+ /// Once invoked, the method will continue until the process itself is
+ /// exiting due to a request to shutdown or some anomaly forces an exit.
+ /// @throw throws a DProcessBaseError if an error is encountered.
+ virtual void run();
+
+ /// @brief Implements the process's shutdown processing. When invoked, it
+ /// should ensure that the process gracefully exits the run method.
+ /// Current implementation simply sets the shutdown flag monitored by the
+ /// run method. @TODO this may need to expand as the implementation evolves.
+ /// @throw throws a DProcessBaseError if an error is encountered.
+ virtual void shutdown();
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Processes the given command.
+ ///
+ /// This method is called to execute any custom commands supported by the
+ /// process. This method must not throw, it should catch any processing
+ /// errors and return a success or failure answer as described below.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr command(const std::string& command,
+ isc::data::ConstElementPtr args);
+ /// @brief Destructor
+ virtual ~D2Process();
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc
new file mode 100644
index 0000000..eb83225
--- /dev/null
+++ b/src/bin/d2/d_controller.cc
@@ -0,0 +1,444 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <d2/d2_log.h>
+#include <d2/d_controller.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+DControllerBasePtr DControllerBase::controller_;
+
+// Note that the constructor instantiates the controller's primary IOService.
+DControllerBase::DControllerBase(const char* name)
+ : name_(name), stand_alone_(false), verbose_(false),
+ spec_file_name_(""), io_service_(new isc::asiolink::IOService()){
+}
+
+void
+DControllerBase::setController(DControllerBase* controller) {
+ if (controller_) {
+ // This shouldn't happen, but let's make sure it can't be done.
+ // It represents a programmatic error.
+ isc_throw (DControllerBaseError,
+ "Multiple controller instances attempted.");
+ }
+
+ controller_ = DControllerBasePtr(controller);
+}
+
+int
+DControllerBase::launch(int argc, char* argv[]) {
+ int ret = d2::NORMAL_EXIT;
+
+ // Step 1 is to parse the command line arguments.
+ try {
+ parseArgs(argc, argv);
+ } catch (const InvalidUsage& ex) {
+ usage(ex.what());
+ return (d2::INVALID_USAGE);
+ }
+
+#if 1
+ //@TODO During initial development default to max log, no buffer
+ isc::log::initLogger(name_, isc::log::DEBUG,
+ isc::log::MAX_DEBUG_LEVEL, NULL, false);
+#else
+ // Now that we know what the mode flags are, we can init logging.
+ // If standalone is enabled, do not buffer initial log messages
+ isc::log::initLogger(name_,
+ ((verbose_ && stand_alone_)
+ ? isc::log::DEBUG : isc::log::INFO),
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+#endif
+
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_STARTING).arg(getpid());
+ try {
+ // Step 2 is to create and initialize the application process.
+ initProcess();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(d2_logger, D2CTL_SESSION_FAIL).arg(ex.what());
+ return (PROCESS_INIT_ERROR);
+ }
+
+ // Next we connect if we are running integrated.
+ if (stand_alone_) {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_STANDALONE);
+ } else {
+ try {
+ establishSession();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(d2_logger, D2CTL_SESSION_FAIL).arg(ex.what());
+ return (d2::SESSION_START_ERROR);
+ }
+ }
+
+ // Everything is clear for launch, so start the application's
+ // event loop.
+ try {
+ runProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, D2CTL_FAILED).arg(ex.what());
+ ret = d2::RUN_ERROR;
+ }
+
+ // If running integrated, always try to disconnect.
+ if (!stand_alone_) {
+ try {
+ disconnectSession();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(d2_logger, D2CTL_DISCONNECT_FAIL).arg(ex.what());
+ ret = d2::SESSION_END_ERROR;
+ }
+ }
+
+ // All done, so bail out.
+ LOG_INFO(d2_logger, D2CTL_STOPPING);
+ return (ret);
+}
+
+void
+DControllerBase::parseArgs(int argc, char* argv[])
+{
+ // Iterate over the given command line options. If its a stock option
+ // ("s" or "v") handle it here. If its a valid custom option, then
+ // invoke customOption.
+ int ch;
+ opterr = 0;
+ optind = 1;
+ std::string opts(":vs" + getCustomOpts());
+ while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+ switch (ch) {
+ case 'v':
+ // Enables verbose logging.
+ verbose_ = true;
+ break;
+
+ case 's':
+ // Enables stand alone or "BINDLESS" operation.
+ stand_alone_ = true;
+ break;
+
+ case '?': {
+ // We hit an invalid option.
+ std::stringstream tmp;
+ tmp << " unsupported option: [" << (char)optopt << "] "
+ << (!optarg ? "" : optarg);
+
+ isc_throw(InvalidUsage,tmp.str());
+ break;
+ }
+
+ default:
+ // We hit a valid custom option
+ if (!customOption(ch, optarg)) {
+ // This would be a programmatic error.
+ std::stringstream tmp;
+ tmp << " Option listed but implemented?: [" <<
+ (char)ch << "] " << (!optarg ? "" : optarg);
+ isc_throw(InvalidUsage,tmp.str());
+ }
+ break;
+ }
+ }
+
+ // There was too much information on the command line.
+ if (argc > optind) {
+ std::stringstream tmp;
+ tmp << "extraneous command line information";
+ isc_throw(InvalidUsage,tmp.str());
+ }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/)
+{
+ // Default implementation returns false.
+ return (false);
+}
+
+void
+DControllerBase::initProcess() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_INIT_PROCESS);
+
+ // Invoke virtual method to instantiate the application process.
+ try {
+ process_.reset(createProcess());
+ } catch (const std::exception& ex) {
+ isc_throw (DControllerBaseError, std::string("createProcess failed:")
+ + ex.what());
+ }
+
+ // This is pretty unlikely, but will test for it just to be safe..
+ if (!process_) {
+ isc_throw (DControllerBaseError, "createProcess returned NULL");
+ }
+
+ // Invoke application's init method (Note this call should throw
+ // DProcessBaseError if it fails).
+ process_->init();
+}
+
+void
+DControllerBase::establishSession() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CCSESSION_STARTING)
+ .arg(spec_file_name_);
+
+ // Create the BIND10 command control session with the our IOService.
+ cc_session_ = SessionPtr(new isc::cc::Session(
+ io_service_->get_io_service()));
+
+ // Create the BIND10 config session with the stub configuration handler.
+ // This handler is internally invoked by the constructor and on success
+ // the constructor updates the current session with the configuration that
+ // had been committed in the previous session. If we do not install
+ // the dummy handler, the previous configuration would be lost.
+ config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
+ spec_file_name_, *cc_session_,
+ dummyConfigHandler, commandHandler,
+ false));
+ // Enable configuration even processing.
+ config_session_->start();
+
+ // We initially create ModuleCCSession() with a dummy configHandler, as
+ // the session module is too eager to send partial configuration.
+ // Replace the dummy config handler with the real handler.
+ config_session_->setConfigHandler(configHandler);
+
+ // Call the real configHandler with the full configuration retrieved
+ // from the config session.
+ isc::data::ConstElementPtr answer = configHandler(
+ config_session_->getFullConfig());
+
+ // Parse the answer returned from the configHandler. Log the error but
+ // keep running. This provides an opportunity for the user to correct
+ // the configuration dynamically.
+ int ret = 0;
+ isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
+ if (ret) {
+ LOG_ERROR(d2_logger, D2CTL_CONFIG_LOAD_FAIL).arg(comment->str());
+ }
+
+ // Lastly, call onConnect. This allows deriving class to execute custom
+ // logic predicated by session connect.
+ onSessionConnect();
+}
+
+void
+DControllerBase::runProcess() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_RUN_PROCESS);
+ if (!process_) {
+ // This should not be possible.
+ isc_throw(DControllerBaseError, "Process not initialized");
+ }
+
+ // Invoke the application process's run method. This may throw
+ // DProcessBaseError
+ process_->run();
+}
+
+void DControllerBase::disconnectSession() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CCSESSION_ENDING);
+
+ // Call virtual onDisconnect. Allows deriving class to execute custom
+ // logic prior to session loss.
+ onSessionDisconnect();
+
+ // Destroy the BIND10 config session.
+ if (config_session_) {
+ config_session_.reset();
+ }
+
+ // Destroy the BIND10 command and control session.
+ if (cc_session_) {
+ cc_session_->disconnect();
+ cc_session_.reset();
+ }
+}
+
+isc::data::ConstElementPtr
+DControllerBase::dummyConfigHandler(isc::data::ConstElementPtr) {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2CTL_CONFIG_STUB);
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
+
+ LOG_DEBUG(d2_logger, DBGLVL_COMMAND, D2CTL_CONFIG_UPDATE)
+ .arg(new_config->str());
+
+ if (!controller_) {
+ // This should never happen as we install the handler after we
+ // instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " Controller has not been initialized.");
+ return (answer);
+ }
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->updateConfig(new_config));
+}
+
+// Static callback which invokes non-static handler on singleton
+isc::data::ConstElementPtr
+DControllerBase::commandHandler(const std::string& command,
+ isc::data::ConstElementPtr args) {
+
+ LOG_DEBUG(d2_logger, DBGLVL_COMMAND, D2CTL_COMMAND_RECEIVED)
+ .arg(command).arg(args->str());
+
+ if (!controller_ ) {
+ // This should never happen as we install the handler after we
+ // instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Command rejected,"
+ " Controller has not been initialized.");
+ return (answer);
+ }
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->executeCommand(command, args));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
+ isc::data::ConstElementPtr full_config;
+ if (stand_alone_) {
+ // @TODO Until there is a configuration manager to provide retrieval
+ // we'll just assume the incoming config is the full configuration set.
+ // It may also make more sense to isolate the controller from the
+ // configuration manager entirely. We could do something like
+ // process_->getFullConfig() here for stand-alone mode?
+ full_config = new_config;
+ } else {
+ if (!config_session_) {
+ // That should never happen as we install config_handler
+ // after we instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " Session has not started.");
+ return (answer);
+ }
+
+ // Let's get the existing configuration.
+ full_config = config_session_->getFullConfig();
+ }
+
+ // The configuration passed to this handler function is partial.
+ // In other words, it just includes the values being modified.
+ // In the same time, there may be dependencies between various
+ // configuration parsers. For example: the option value can
+ // be set if the definition of this option is set. If someone removes
+ // an existing option definition then the partial configuration that
+ // removes that definition is triggered while a relevant option value
+ // may remain configured. This eventually results in the
+ // configuration being in the inconsistent state.
+ // In order to work around this problem we need to merge the new
+ // configuration with the existing (full) configuration.
+
+ // Let's create a new object that will hold the merged configuration.
+ boost::shared_ptr<isc::data::MapElement>
+ merged_config(new isc::data::MapElement());
+
+ // Merge an existing and new configuration.
+ merged_config->setValue(full_config->mapValue());
+ isc::data::merge(merged_config, new_config);
+
+ // Send the merged configuration to the application.
+ return (process_->configure(merged_config));
+}
+
+
+isc::data::ConstElementPtr
+DControllerBase::executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args) {
+ // Shutdown is universal. If its not that, then try it as
+ // an custom command supported by the derivation. If that
+ // doesn't pan out either, than send to it the application
+ // as it may be supported there.
+ isc::data::ConstElementPtr answer;
+ if (command.compare(SHUT_DOWN_COMMAND) == 0) {
+ answer = shutdown();
+ } else {
+ // It wasn't shutdown, so may be a custom controller command.
+ int rcode = 0;
+ answer = customControllerCommand(command, args);
+ isc::config::parseAnswer(rcode, answer);
+ if (rcode == COMMAND_INVALID)
+ {
+ // It wasn't controller command, so may be an application command.
+ answer = process_->command(command,args);
+ }
+ }
+
+ return (answer);
+}
+
+isc::data::ConstElementPtr
+DControllerBase::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+
+ // Default implementation always returns invalid command.
+ return (isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::shutdown() {
+ // @TODO (tmark) - not sure about io_service_->stop here
+ // IF application is using this service for all of its IO, stopping
+ // here would mean, no more work by the application.. UNLESS it resets
+ // it. People have discussed letting the application finish any in-progress
+ // updates before shutting down. If we don't stop it here, then
+ // application can't use io_service_->run(), it will never "see" the
+ // shutdown.
+ io_service_->stop();
+ if (process_) {
+ process_->shutdown();
+ } else {
+ // Not really a failure, but this condition is worth noting. In reality
+ // it should be pretty hard to cause this.
+ LOG_WARN(d2_logger, D2CTL_NOT_RUNNING);
+ }
+
+ return (isc::config::createAnswer(0, "Shutting down."));
+}
+
+void
+DControllerBase::usage(const std::string & text)
+{
+ if (text != "") {
+ std::cerr << "Usage error:" << text << std::endl;
+ }
+
+ std::cerr << "Usage: " << name_ << std::endl;
+ std::cerr << " -v: verbose output" << std::endl;
+ std::cerr << " -s: stand-alone mode (don't connect to BIND10)"
+ << std::endl;
+
+ std::cerr << getUsageText() << std::endl;
+}
+
+DControllerBase::~DControllerBase() {
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
new file mode 100644
index 0000000..db371a1
--- /dev/null
+++ b/src/bin/d2/d_controller.h
@@ -0,0 +1,497 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d_process.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace isc {
+namespace d2 {
+
+/// @brief DControllerBase launch exit status values. Upon service shutdown
+/// normal or otherwise, the Controller's launch method will return one of
+/// these values.
+
+/// @brief Indicates normal shutdown.
+static const int NORMAL_EXIT = 0;
+/// @brief Indicates invalid command line.
+static const int INVALID_USAGE = 1;
+/// @brief Failed to create and initialize application process.
+static const int PROCESS_INIT_ERROR = 2;
+/// @brief Could not connect to BIND10 (integrated mode only).
+static const int SESSION_START_ERROR = 3;
+/// @brief A fatal error occurred in the application process.
+static const int RUN_ERROR = 4;
+/// @brief Error occurred disconnecting from BIND10 (integrated mode only).
+static const int SESSION_END_ERROR = 5;
+
+/// @brief Exception thrown when the command line is invalid.
+class InvalidUsage : public isc::Exception {
+public:
+ InvalidUsage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+ DControllerBaseError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines a shared pointer to DControllerBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @brief Defines a shared pointer to a Session.
+typedef boost::shared_ptr<isc::cc::Session> SessionPtr;
+
+/// @brief Defines a shared pointer to a ModuleCCSession.
+typedef boost::shared_ptr<isc::config::ModuleCCSession> ModuleCCSessionPtr;
+
+
+/// @brief Application Controller
+///
+/// DControllerBase is an abstract singleton which provides the framework and
+/// services for managing an application process that implements the
+/// DProcessBase interface. It allows the process to run either in
+/// integrated mode as a BIND10 module or stand-alone. It coordinates command
+/// line argument parsing, process instantiation and initialization, and runtime
+/// control through external command and configuration event handling.
+/// It creates the io_service_ instance which is used for runtime control
+/// events and passes the io_service into the application process at process
+/// creation. In integrated mode it is responsible for establishing BIND10
+/// session(s) and passes this io_service_ into the session creation method(s).
+/// It also provides the callback handlers for command and configuration events.
+/// NOTE: Derivations must supply their own static singleton instance method(s)
+/// for creating and fetching the instance. The base class declares the instance
+/// member in order for it to be available for BIND10 callback functions. This
+/// would not be required if BIND10 supported instance method callbacks.
+class DControllerBase : public boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the controller. Typically this
+ /// would be the BIND10 module name.
+ DControllerBase(const char* name);
+
+ /// @brief Destructor
+ virtual ~DControllerBase();
+
+ /// @brief Acts as the primary entry point into the controller execution
+ /// and provides the outermost application control logic:
+ ///
+ /// 1. parse command line arguments
+ /// 2. instantiate and initialize the application process
+ /// 3. establish BIND10 session(s) if in integrated mode
+ /// 4. start and wait on the application process event loop
+ /// 5. upon event loop completion, disconnect from BIND10 (if needed)
+ /// 6. exit to the caller
+ ///
+ /// It is intended to be called from main() and be given the command line
+ /// arguments. Note this method is deliberately not virtual to ensure the
+ /// proper sequence of events occur.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ ///
+ /// @return returns one of the following integer values:
+ /// d2::NORMAL_EXIT - Indicates normal shutdown.
+ /// d2::INVALID_USAGE - Indicates invalid command line.
+ /// d2::PROCESS_INIT_ERROR - Failed to create and initialize application
+ /// process
+ /// d2::SESSION_START_ERROR - Could not connect to BIND10 (integrated mode
+ /// only).
+ /// d2::RUN_ERROR - An fatal error occurred in the application process
+ /// d2::SESSION_END_ERROR = 4;
+ int launch(int argc, char* argv[]);
+
+ /// @brief A dummy configuration handler that always returns success.
+ ///
+ /// This configuration handler does not perform configuration
+ /// parsing and always returns success. A dummy handler should
+ /// be installed using \ref isc::config::ModuleCCSession ctor
+ /// to get the initial configuration. This initial configuration
+ /// comprises values for only those elements that were modified
+ /// the previous session. The D2 configuration parsing can't be
+ /// used to parse the initial configuration because it may need the
+ /// full configuration to satisfy dependencies between the
+ /// various configuration values. Installing the dummy handler
+ /// that guarantees to return success causes initial configuration
+ /// to be stored for the session being created and that it can
+ /// be later accessed with \ref isc::ConfigData::getFullConfig.
+ ///
+ /// @param new_config new configuration.
+ ///
+ /// @return success configuration status.
+ static isc::data::ConstElementPtr
+ dummyConfigHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming configuration updates.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, updateConfig.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config update
+ static isc::data::ConstElementPtr
+ configHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming commands.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, executeCommand.
+ ///
+ /// @param command textual representation of the command
+ /// @param args parameters of the command
+ ///
+ /// @return status of the processed command
+ static isc::data::ConstElementPtr
+ commandHandler(const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration update. Provides behavioral
+ /// path for both integrated and stand-alone modes. The current
+ /// implementation will merge the configuration update into the existing
+ /// configuration and then invoke the application process' configure method.
+ ///
+ /// @TODO This implementation is will evolve as the D2 configuration
+ /// management task is implemented (trac #2957).
+ ///
+ /// @param new_config is the new configuration
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ updateConfig(isc::data::ConstElementPtr new_config);
+
+
+ /// @brief Instance method invoked by the command event handler and which
+ /// processes the actual command directive.
+ ///
+ /// It supports the execution of:
+ ///
+ /// 1. Stock controller commands - commands common to all DControllerBase
+ /// derivations. Currently there is only one, the shutdown command.
+ ///
+ /// 2. Custom controller commands - commands that the deriving controller
+ /// class implements. These commands are executed by the deriving
+ /// controller.
+ ///
+ /// 3. Custom application commands - commands supported by the application
+ /// process implementation. These commands are executed by the application
+ /// process.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as valid be either
+ /// the controller or the application process.
+ virtual isc::data::ConstElementPtr
+ executeCommand(const std::string& command, isc::data::ConstElementPtr args);
+
+protected:
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support additional command line options. It is invoked during command
+ /// line argument parsing (see parseArgs method) if the option is not
+ /// recognized as a stock DControllerBase option.
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return must return true if the option was valid, false is it is
+ /// invalid. (Note the default implementation always returns false.)
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Abstract method that is responsible for instantiating the
+ /// application process instance. It is invoked by the controller after
+ /// command line argument parsing as part of the process initialization
+ /// (see initProcess method).
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if the create fails.
+ /// Note this value is subsequently wrapped in a smart pointer.
+ virtual DProcessBase* createProcess() = 0;
+
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support custom external commands executed by the controller. This
+ /// method is invoked by the processCommand if the received command is
+ /// not a stock controller command.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as a valid custom
+ /// controller command.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Virtual method which is invoked after the controller successfully
+ /// establishes BIND10 connectivity. It provides an opportunity for the
+ /// derivation to execute any custom behavior associated with session
+ /// establishment.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should throw a DControllerBaseError if it fails.
+ virtual void onSessionConnect(){};
+
+ /// @brief Virtual method which is invoked as the first action taken when
+ /// the controller is terminating the session(s) with BIND10. It provides
+ /// an opportunity for the derivation to execute any custom behavior
+ /// associated with session termination.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should throw a DControllerBaseError if it fails.
+ virtual void onSessionDisconnect(){};
+
+ /// @brief Virtual method which can be used to contribute derivation
+ /// specific usage text. It is invoked by the usage() method under
+ /// invalid usage conditions.
+ ///
+ /// @return returns the desired text.
+ virtual const std::string getUsageText() {
+ return ("");
+ }
+
+ /// @brief Virtual method which returns a string containing the option
+ /// letters for any custom command line options supported by the derivation.
+ /// These are added to the stock options of "s" and "v" during command
+ /// line interpretation.
+ ///
+ /// @return returns a string containing the custom option letters.
+ virtual const std::string getCustomOpts() {
+ return ("");
+ }
+
+ /// @brief Supplies the controller name.
+ ///
+ /// @return returns the controller name string
+ const std::string& getName() {
+ return (name_);
+ }
+
+ /// @brief Supplies whether or not the controller is in stand alone mode.
+ ///
+ /// @return returns true if in stand alone mode, false otherwise
+ bool isStandAlone() {
+ return (stand_alone_);
+ }
+
+ /// @brief Method for enabling or disabling stand alone mode.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setStandAlone(bool value) {
+ stand_alone_ = value;
+ }
+
+ /// @brief Supplies whether or not verbose logging is enabled.
+ ///
+ /// @return returns true if verbose logging is enabled.
+ bool isVerbose() {
+ return (verbose_);
+ }
+
+ /// @brief Method for enabling or disabling verbose logging.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setVerbose(bool value) {
+ verbose_ = value;
+ }
+
+ /// @brief Getter for fetching the controller's IOService
+ ///
+ /// @return returns a pointer reference to the IOService.
+ IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Getter for fetching the name of the controller's BIND10 spec
+ /// file.
+ ///
+ /// @return returns the file name string.
+ const std::string& getSpecFileName() {
+ return (spec_file_name_);
+ }
+
+ /// @brief Setter for setting the name of the controller's BIND10 spec file.
+ ///
+ /// @param value is the file name string.
+ void setSpecFileName(const std::string& spec_file_name) {
+ spec_file_name_ = spec_file_name;
+ }
+
+ /// @brief Static getter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ static DControllerBasePtr& getController() {
+ return (controller_);
+ }
+
+ /// @brief Static setter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ /// @throw throws DControllerBase error if an attempt is made to set the
+ /// instance a second time.
+ static void setController(DControllerBase* controller);
+
+private:
+ /// @brief Processes the command line arguments. It is the first step
+ /// taken after the controller has been launched. It combines the stock
+ /// list of options with those returned by getCustomOpts(), and uses
+ /// cstdlib's getopt to loop through the command line. The stock options
+ /// It handles stock options directly, and passes any custom options into
+ /// the customOption method. Currently there are only two stock options
+ /// -s for stand alone mode, and -v for verbose logging.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ ///
+ /// @throw throws InvalidUsage when there are usage errors.
+ void parseArgs(int argc, char* argv[]);
+
+ /// @brief Instantiates the application process and then initializes it.
+ /// This is the second step taken during launch, following successful
+ /// command line parsing. It is used to invoke the derivation-specific
+ /// implementation of createProcess, following by an invoking of the
+ /// newly instantiated process's init method.
+ ///
+ /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+ /// if there is a failure creating or initializing the application process.
+ void initProcess();
+
+ /// @brief Establishes connectivity with BIND10. This method is used
+ /// invoked during launch, if running in integrated mode, following
+ /// successful process initialization. It is responsible for establishing
+ /// the BIND10 control and config sessions. During the session creation,
+ /// it passes in the controller's IOService and the callbacks for command
+ /// directives and config events. Lastly, it will invoke the onConnect
+ /// method providing the derivation an opportunity to execute any custom
+ /// logic associated with session establishment.
+ ///
+ /// @throw the BIND10 framework may throw std::exceptions.
+ void establishSession();
+
+ /// @brief Invokes the application process's event loop,(DBaseProcess::run).
+ /// It is called during launch only after successfully completing the
+ /// requested setup: command line parsing, application initialization,
+ /// and session establishment (if not stand-alone).
+ /// The process event loop is expected to only return upon application
+ /// shutdown either in response to the shutdown command or due to an
+ /// unrecoverable error.
+ ///
+ // @throw throws DControllerBaseError or indirectly DProcessBaseError
+ void runProcess();
+
+ /// @brief Terminates connectivity with BIND10. This method is invoked
+ /// in integrated mode after the application event loop has exited. It
+ /// first calls the onDisconnect method providing the derivation an
+ /// opportunity to execute custom logic if needed, and then terminates the
+ /// BIND10 config and control sessions.
+ ///
+ /// @throw the BIND10 framework may throw std:exceptions.
+ void disconnectSession();
+
+ /// @brief Initiates shutdown procedure. This method is invoked
+ /// by executeCommand in response to the shutdown command. It will invoke
+ /// the application process's shutdown method, which causes the process to
+ /// exit it's event loop.
+ ///
+ /// @return returns an Element that contains the results of shutdown
+ /// attempt composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ isc::data::ConstElementPtr shutdown();
+
+ /// @brief Prints the program usage text to std error.
+ ///
+ /// @param text is a string message which will preceded the usage text.
+ /// This is intended to be used for specific usage violation messages.
+ void usage(const std::string & text);
+
+private:
+ /// @brief Text label for the controller. Typically this would be the
+ /// BIND10 module name.
+ std::string name_;
+
+ /// @brief Indicates if the controller stand alone mode is enabled. When
+ /// enabled, the controller will not establish connectivity with BIND10.
+ bool stand_alone_;
+ /// @brief Indicates if the verbose logging mode is enabled.
+
+ bool verbose_;
+ /// @brief The absolute file name of the BIND10 spec file.
+ std::string spec_file_name_;
+
+ /// @brief Pointer to the instance of the process.
+ ///
+ /// This is required for config and command handlers to gain access to
+ /// the process
+ DProcessBasePtr process_;
+
+ /// @brief Shared pointer to an IOService object, used for ASIO operations.
+ IOServicePtr io_service_;
+
+ /// @brief Helper session object that represents raw connection to msgq.
+ SessionPtr cc_session_;
+
+ /// @brief Session that receives configuration and commands.
+ ModuleCCSessionPtr config_session_;
+
+ /// @brief Singleton instance value.
+ static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to facilitate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
new file mode 100644
index 0000000..73bbc17
--- /dev/null
+++ b/src/bin/d2/d_process.h
@@ -0,0 +1,169 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_PROCESS_H
+#define D_PROCESS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the process encountered an operational error.
+class DProcessBaseError : public isc::Exception {
+public:
+ DProcessBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+static const int COMMAND_SUCCESS = 0;
+static const int COMMAND_ERROR = 1;
+static const int COMMAND_INVALID = 2;
+static const std::string SHUT_DOWN_COMMAND("shutdown");
+
+/// @brief Application Process Interface
+///
+/// DProcessBase is an abstract class represents the primary "application"
+/// level object in a "managed" asynchronous application. It provides a uniform
+/// interface such that a managing layer can construct, initialize, and start
+/// the application's event loop. The event processing is centered around the
+/// use of isc::asiolink::io_service. The io_service is shared between the
+/// managing layer and the DProcessBase. This allows management layer IO such
+/// as directives to be sensed and handled, as well as processing IO activity
+/// specific to the application. In terms of management layer IO, there are
+/// methods shutdown, configuration updates, and commands unique to the
+/// application.
+class DProcessBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DProcessBase(const char* name, IOServicePtr io_service) : name_(name),
+ io_service_(io_service), shut_down_flag_(false) {
+
+ if (!io_service_) {
+ isc_throw (DProcessBaseError, "IO Service cannot be null");
+ }
+ };
+
+ /// @brief May be used after instantiation to perform initialization unique
+ /// to application. It must be invoked prior to invoking run. This would
+ /// likely include the creation of additional IO sources and their
+ /// integration into the io_service.
+ /// @throw throws a DProcessBaseError if the initialization fails.
+ virtual void init() = 0;
+
+ /// @brief Implements the process's event loop. In its simplest form it
+ /// would an invocation io_service_->run(). This method should not exit
+ /// until the process itself is exiting due to a request to shutdown or
+ /// some anomaly is forcing an exit.
+ /// @throw throws a DProcessBaseError if an operational error is encountered.
+ virtual void run() = 0;
+
+ /// @brief Implements the process's shutdown processing. When invoked, it
+ /// should ensure that the process gracefully exits the run method.
+ /// @throw throws a DProcessBaseError if an operational error is encountered.
+ virtual void shutdown() = 0;
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set) = 0;
+
+ /// @brief Processes the given command.
+ ///
+ /// This method is called to execute any custom commands supported by the
+ /// process. This method must not throw, it should catch any processing
+ /// errors and return a success or failure answer as described below.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr command(
+ const std::string& command, isc::data::ConstElementPtr args) = 0;
+
+ /// @brief Destructor
+ virtual ~DProcessBase(){};
+
+ /// @brief Checks if the process has been instructed to shut down.
+ ///
+ /// @return returns true if process shutdown flag is true.
+ bool shouldShutdown() {
+ return (shut_down_flag_);
+ }
+
+ /// @brief Sets the process shut down flag to the given value.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setShutdownFlag(bool value) {
+ shut_down_flag_ = value;
+ }
+
+ /// @brief Fetches the name of the controller.
+ ///
+ /// @return returns a reference the controller's name string.
+ const std::string& getName() const {
+ return (name_);
+ }
+
+ /// @brief Fetches the controller's IOService.
+ ///
+ /// @return returns a reference to the controller's IOService.
+ IOServicePtr& getIoService() {
+ return (io_service_);
+ }
+
+private:
+ /// @brief Text label for the process. Generally used in log statements,
+ /// but otherwise can be arbitrary.
+ std::string name_;
+
+ /// @brief The IOService to be used for asynchronous event handling.
+ IOServicePtr io_service_;
+
+ /// @brief Boolean flag set when shutdown has been requested.
+ bool shut_down_flag_;
+};
+
+/// @brief Defines a shared pointer to DProcessBase.
+typedef boost::shared_ptr<DProcessBase> DProcessBasePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/main.cc b/src/bin/d2/main.cc
index 7ef300b..e0e979b 100644
--- a/src/bin/d2/main.cc
+++ b/src/bin/d2/main.cc
@@ -14,6 +14,7 @@
#include <config.h>
#include <d2/d2_log.h>
+#include <d2/d2_controller.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
@@ -22,76 +23,26 @@
using namespace isc::d2;
using namespace std;
-/// This file contains entry point (main() function) for standard DHCP-DDNS
-/// process, b10-d2, component for BIND10 framework. It parses command-line
-/// arguments and instantiates D2Controller class that is responsible for
-/// establishing connection with msgq (receiving commands and configuration)
-/// and also creating D2Server object as well.
-///
-/// For detailed explanation or relations between main(), D2Controller,
-/// D2Server and other classes, see \ref d2Session.
-
-namespace {
-
-const char* const D2_NAME = "b10-d2";
-
-void
-usage() {
- cerr << "Usage: " << D2_NAME << " [-v] [-s]" << endl;
- cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
- cerr << " -v: verbose output (only when in stand-alone mode" << endl;
- exit(EXIT_FAILURE);
-}
-} // end of anonymous namespace
-
+/// This file contains entry point (main() function) for standard DHCP-DDNS
+/// process, b10-dhcp-ddns, component for BIND10 framework. It fetches
+/// the D2Controller singleton instance and invokes its launch method.
+/// The exit value of the program will the return value of launch:
+/// d2::NORMAL_EXIT - Indicates normal shutdown.
+/// d2::INVALID_USAGE - Indicates invalid command line.
+/// d2::PROCESS_INIT_ERROR - Failed to create and initialize application
+/// process
+/// d2::SESSION_START_ERROR - Could not connect to BIND10 (integrated mode
+/// only).
+/// d2::RUN_ERROR - A fatal error occurred in the application process
+/// d2::SESSION_END_ERROR - Error occurred disconnecting from BIND10 (integrated
+/// mode only).
int
main(int argc, char* argv[]) {
- int ch;
-
- // @TODO NOTE these parameters are preliminary only. They are here to
- // for symmetry with the DHCP servers. They may or may not
- // become part of the eventual implementation.
- bool stand_alone = false; // Should be connect to BIND10 msgq?
- bool verbose_mode = false; // Should server be verbose?
+ // Instantiate/fetch the DHCP-DDNS application controller singleton.
+ DControllerBasePtr& controller = D2Controller::instance();
- while ((ch = getopt(argc, argv, "vsp:")) != -1) {
- switch (ch) {
- case 'v':
- verbose_mode = true;
- break;
-
- case 's':
- stand_alone = true;
- break;
-
- default:
- usage();
- }
- }
-
- // Check for extraneous parameters.
- if (argc > optind) {
- usage();
- }
-
- // Initialize logging. If verbose, we'll use maximum verbosity.
- // If standalone is enabled, do not buffer initial log messages
- // Verbose logging is only enabled when in stand alone mode.
- isc::log::initLogger(D2_NAME,
- ((verbose_mode && stand_alone)
- ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
- LOG_INFO(d2_logger, D2_STARTING);
- LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2_START_INFO)
- .arg(getpid()).arg(verbose_mode ? "yes" : "no")
- .arg(stand_alone ? "yes" : "no" );
-
- // For now we will sleep awhile to simulate doing something.
- // Without at least a sleep, the process will start, exit and be
- // restarted by Bind10/Init endlessley in a rapid succession.
- sleep(1000);
- LOG_INFO(d2_logger, D2_SHUTDOWN);
- return (EXIT_SUCCESS);
+ // Launch the controller passing in command line arguments.
+ // Exit program with the controller's return code.
+ return (controller->launch(argc, argv));
}
-
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index 72e6254..bd7773b 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -52,7 +52,15 @@ if HAVE_GTEST
TESTS += d2_unittests
d2_unittests_SOURCES = ../d2_log.h ../d2_log.cc
+d2_unittests_SOURCES += ../d_process.h
+d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
+d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
d2_unittests_SOURCES += d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
+d2_unittests_SOURCES += d_controller_unittests.cc
+d2_unittests_SOURCES += d2_controller_unittests.cc
nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -60,6 +68,9 @@ d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
d2_unittests_LDADD = $(GTEST_LDADD)
d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
new file mode 100644
index 0000000..c230e36
--- /dev/null
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -0,0 +1,215 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/d2_controller.h>
+#include <d2/spec_config.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing D2Controller class. This class
+/// derives from DControllerTest and wraps a D2Controller. Much of the
+/// underlying functionality is in the DControllerBase class which has an
+/// extensive set of unit tests that are independent of DHCP-DDNS.
+/// @TODO Currently These tests are relatively light and duplicate some of
+/// the testing done on the base class. These tests are sufficient to ensure
+/// that D2Controller properly derives from its base class and to test the
+/// logic that is unique to D2Controller. These tests will be augmented and
+/// or new tests added as additional functionality evolves.
+/// Unlike the stub testing, there is no use of SimFailure to induce error
+/// conditions as this is production code.
+class D2ControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor
+ /// Note the constructor passes in the static D2Controller instance
+ /// method.
+ D2ControllerTest() : DControllerTest(D2Controller::instance) {
+ }
+
+ /// @brief Destructor
+ ~D2ControllerTest() {
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verfies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(D2ControllerTest, basicInstanceTesting) {
+ // Verify the we can the singleton instance can be fetched and that
+ // it is the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));
+
+ // Verify that controller's name is correct.
+ EXPECT_TRUE(checkName(D2_MODULE_NAME));
+
+ // Verify that controller's spec file name is correct.
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verfies that:
+/// 1. Standard command line options are supported.
+/// 2. Invalid options are detected.
+TEST_F(D2ControllerTest, commandLineArgs) {
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Verify that both flags are false initially.
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that flags are now true.
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { (char*)"progName", (char*)"-x" };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that the process can be successfully created and initialized.
+TEST_F(D2ControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+/// Launch exit code should be d2::NORMAL_EXIT.
+TEST_F(D2ControllerTest, launchNormalShutdown) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(d2::NORMAL_EXIT, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation. Note that this testing calls the configuration update event
+/// callback, configHandler, directly.
+/// This test verifies that:
+/// 1. Configuration will be rejected in integrated mode when there is no
+/// session established. (This is a very contrived situation).
+/// 2. In stand-alone mode a configuration update results in successful
+/// status return.
+/// 3. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(D2ControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+}
+
+/// @brief Command execution tests.
+/// This really tests just the ability of the handler to invoke the necessary
+/// chain of methods and to handle error conditions. Note that this testing
+/// calls the command callback, commandHandler, directly.
+/// This test verifies that:
+/// 1. That an unrecognized command is detected and returns a status of
+/// d2::COMMAND_INVALID.
+/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
+TEST_F(D2ControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that an unknown command returns an COMMAND_INVALID response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+
+ // Verify that shutdown command returns COMMAND_SUCCESS response.
+ //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
new file mode 100644
index 0000000..40c85f3
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <config/ccsession.h>
+#include <d2/d2_process.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief D2Process test fixture class
+class D2ProcessTest : public ::testing::Test {
+public:
+
+ /// @brief Static instance accessible via test callbacks.
+ static DProcessBasePtr process_;
+
+ /// @brief Constructor
+ D2ProcessTest() {
+ io_service_.reset(new isc::asiolink::IOService());
+ process_.reset(new D2Process("TestProcess", io_service_));
+ }
+
+ /// @brief Destructor
+ ~D2ProcessTest() {
+ io_service_.reset();
+ process_.reset();
+ }
+
+ /// @brief Callback that will invoke shutdown method.
+ static void genShutdownCallback() {
+ process_->shutdown();
+ }
+
+ /// @brief Callback that throws an exception.
+ static void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief IOService for event processing. Fills in for IOService
+ /// supplied by management layer.
+ IOServicePtr io_service_;
+};
+
+// Define the static process instance
+DProcessBasePtr D2ProcessTest::process_;
+
+
+/// @brief Verifies D2Process constructor behavior.
+/// 1. Verifies that constructor fails with an invalid IOService
+/// 2. Verifies that constructor succeeds with a valid IOService
+TEST(D2Process, construction) {
+ // Verify that the constructor will fail if given an empty
+ // io service.
+ IOServicePtr lcl_io_service;
+ EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
+
+ // Verify that the constructor succeeds with a valid io_service
+ lcl_io_service.reset(new isc::asiolink::IOService());
+ ASSERT_NO_THROW (D2Process("TestProcess", lcl_io_service));
+}
+
+/// @brief Verifies basic configure method behavior.
+/// @TODO This test is simplistic and will need to be augmented as configuration
+/// ability is implemented.
+TEST_F(D2ProcessTest, configure) {
+ // Verify that given a configuration "set", configure returns
+ // a successful response.
+ int rcode = -1;
+ string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr json = isc::data::Element::fromJSON(config);
+ isc::data::ConstElementPtr answer = process_->configure(json);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+}
+
+/// @brief Verifies basic command method behavior.
+/// @TODO IF the D2Process is extended to support extra commands this testing
+/// will need to augmented accordingly.
+TEST_F(D2ProcessTest, command) {
+ // Verify that the process will process unsupported command and
+ // return a failure response.
+ int rcode = -1;
+ string args = "{ \"arg1\": 77 } ";
+ isc::data::ElementPtr json = isc::data::Element::fromJSON(args);
+ isc::data::ConstElementPtr answer =
+ process_->command("bogus_command", json);
+ parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+}
+
+/// @brief Verifies that an "external" call to shutdown causes the run method
+/// to exit gracefully.
+TEST_F(D2ProcessTest, normalShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*io_service_);
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(process_->run());
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Verifies that an "uncaught" exception thrown during event loop
+/// execution is treated as a fatal error.
+TEST_F(D2ProcessTest, fatalErrorShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // the exception. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*io_service_);
+ timer.setup(genFatalErrorCallback, 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_THROW(process_->run(), DProcessBaseError);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the anomaly occurred
+ // during io callback processing.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_test.py b/src/bin/d2/tests/d2_test.py
index ce7bdc3..bcf3815 100644
--- a/src/bin/d2/tests/d2_test.py
+++ b/src/bin/d2/tests/d2_test.py
@@ -159,9 +159,9 @@ class TestD2Daemon(unittest.TestCase):
print("Note: Simple test to verify that D2 server can be started.")
# note that "-s" for stand alone is necessary in order to flush the log output
# soon enough to catch it.
- (returncode, output, error) = self.runCommand(["../b10-d2", "-s"])
+ (returncode, output, error) = self.runCommand(["../b10-d2", "-s", "-v"])
output_text = str(output) + str(error)
- self.assertEqual(output_text.count("D2_STARTING"), 1)
+ self.assertEqual(output_text.count("D2CTL_STARTING"), 1)
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/d2/tests/d_controller_unittests.cc b/src/bin/d2/tests/d_controller_unittests.cc
new file mode 100644
index 0000000..69dcedc
--- /dev/null
+++ b/src/bin/d2/tests/d_controller_unittests.cc
@@ -0,0 +1,368 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/spec_config.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing DControllerBase class. This class
+/// derives from DControllerTest and wraps a DStubController. DStubController
+/// has been constructed to exercise DControllerBase.
+class DStubControllerTest : public DControllerTest {
+public:
+
+ /// @brief Constructor.
+ /// Note the constructor passes in the static DStubController instance
+ /// method.
+ DStubControllerTest() : DControllerTest (DStubController::instance) {
+ }
+
+ virtual ~DStubControllerTest() {
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verfies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(DStubControllerTest, basicInstanceTesting) {
+ // Verify that the singleton exists and it is the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<DStubController>(controller));
+
+ // Verify that controller's name is correct.
+ EXPECT_TRUE(checkName(D2_MODULE_NAME));
+
+ // Verify that controller's spec file name is correct.
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Custom command line options are supported.
+/// 3. Invalid options are detected.
+/// 4. Extraneous command line information is detected.
+TEST_F(DStubControllerTest, commandLineArgs) {
+
+ // Verify that both flags are false initially.
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that flags are now true.
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify that the custom command line option is parsed without error.
+ char xopt[3]="";
+ sprintf (xopt, "-%c", *DStubController::stub_option_x_);
+ char* argv1[] = { (char*)"progName", xopt};
+ argc = 2;
+ EXPECT_NO_THROW (parseArgs(argc, argv1));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { (char*)"progName", (char*)"-bs" };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+
+ // Verify that extraneous information is detected.
+ char* argv3[] = { (char*)"progName", (char*)"extra", (char*)"information" };
+ argc = 3;
+ EXPECT_THROW (parseArgs(argc, argv3), InvalidUsage);
+
+
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that:
+/// 1. An error during process creation is handled.
+/// 2. A NULL returned by process creation is handled.
+/// 3. An error during process initialization is handled.
+/// 4. Process can be successfully created and initialized.
+TEST_F(DStubControllerTest, initProcessTesting) {
+ // Verify that a failure during process creation is caught.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that a NULL returned by process creation is handled.
+ SimFailure::set(SimFailure::ftCreateProcessNull);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that an error during process initialization is handled.
+ SimFailure::set(SimFailure::ftProcessInit);
+ EXPECT_THROW(initProcess(), DProcessBaseError);
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that the application process can created and initialized.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch handling of invalid command line.
+/// This test launches with an invalid command line which should exit with
+/// an status of d2::INVALID_USAGE.
+TEST_F(DStubControllerTest, launchInvalidUsage) {
+ // Command line to run integrated
+ char* argv[] = { (char*)"progName",(char*) "-z" };
+ int argc = 2;
+
+ // Launch the controller in integrated mode.
+ int rcode = launch(argc, argv);
+
+ // Verify session failure exit status.
+ EXPECT_EQ(d2::INVALID_USAGE, rcode);
+}
+
+/// @brief Tests launch handling of failure in application process
+/// initialization. This test launches with a valid command line but with
+/// SimFailure set to fail during process creation. Launch exit code should
+/// be d2::PROCESS_INIT_ERROR.
+TEST_F(DStubControllerTest, launchProcessInitError) {
+ // Command line to run integrated
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Launch the controller in stand alone mode.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ int rcode = launch(argc, argv);
+
+ // Verify session failure exit status.
+ EXPECT_EQ(d2::PROCESS_INIT_ERROR, rcode);
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+/// Launch exit code should be d2::NORMAL_EXIT.
+TEST_F(DStubControllerTest, launchNormalShutdown) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify normal shutdown status.
+ EXPECT_EQ(d2::NORMAL_EXIT, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Tests launch with an operational error during application execution.
+/// This test creates an interval timer to generate a runtime exception during
+/// the process event loop. It launches wih a valid, stand-alone command line
+/// and no simulated errors. Launch exit code should be d2::RUN_ERROR.
+TEST_F(DStubControllerTest, launchRuntimeError) {
+ // command line to run standalone
+ char* argv[] = { (char*)"progName", (char*)"-s", (char*)"-v" };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genFatalErrorCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ int rcode = launch(argc, argv);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify abnormal shutdown status.
+ EXPECT_EQ(d2::RUN_ERROR, rcode);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Tests launch with a session establishment failure.
+/// This test launches with a valid command line for integrated mode and no.
+/// Attempting to connect to BIND10 should fail, even if BIND10 is running
+/// UNLESS the test is run as root. Launch exit code should be
+/// d2::SESSION_START_ERROR.
+TEST_F(DStubControllerTest, launchSessionFailure) {
+ // Command line to run integrated
+ char* argv[] = { (char*)"progName" };
+ int argc = 1;
+
+ // Launch the controller in integrated mode.
+ int rcode = launch(argc, argv);
+
+ // Verify session failure exit status.
+ EXPECT_EQ(d2::SESSION_START_ERROR, rcode);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation. Note that this testing calls the configuration update event
+/// callback, configHandler, directly.
+/// This test verifies that:
+/// 1. Configuration will be rejected in integrated mode when there is no
+/// session established. (This is a very contrived situation).
+/// 2. In stand-alone mode a configuration update results in successful
+/// status return.
+/// 3. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(DStubControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that an error in process configure method is handled.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+/// @brief Command execution tests.
+/// This really tests just the ability of the handler to invoke the necessary
+/// chain of methods and to handle error conditions. Note that this testing
+/// calls the command callback, commandHandler, directly.
+/// This test verifies that:
+/// 1. That an unrecognized command is detected and returns a status of
+/// d2::COMMAND_INVALID.
+/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
+/// 3. A valid, custom controller command is recognized a d2::COMMAND_SUCCESS
+/// status.
+/// 4. A valid, custom process command is recognized a d2::COMMAND_SUCCESS
+/// status.
+/// 5. That a valid controller command that fails returns a d2::COMMAND_ERROR.
+/// 6. That a valid process command that fails returns a d2::COMMAND_ERROR.
+TEST_F(DStubControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that an unknown command returns an d2::COMMAND_INVALID response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+
+ // Verify that shutdown command returns d2::COMMAND_SUCCESS response.
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom controller command returns
+ // d2::COMMAND_SUCCESS response.
+ answer = DControllerBase::commandHandler(DStubController::
+ stub_ctl_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom process command returns d2::COMMAND_SUCCESS
+ // response.
+ answer = DControllerBase::commandHandler(DStubProcess::
+ stub_proc_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom controller command that fails returns
+ // a d2::COMMAND_ERROR.
+ SimFailure::set(SimFailure::ftControllerCommand);
+ answer = DControllerBase::commandHandler(DStubController::
+ stub_ctl_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_ERROR, rcode);
+
+ // Verify that a valid custom process command that fails returns
+ // a d2::COMMAND_ERROR.
+ SimFailure::set(SimFailure::ftProcessCommand);
+ answer = DControllerBase::commandHandler(DStubProcess::
+ stub_proc_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_ERROR, rcode);
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d_test_stubs.cc b/src/bin/d2/tests/d_test_stubs.cc
new file mode 100644
index 0000000..1b71e27
--- /dev/null
+++ b/src/bin/d2/tests/d_test_stubs.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/spec_config.h>
+#include <d2/tests/d_test_stubs.h>
+
+using namespace asio;
+
+namespace isc {
+namespace d2 {
+
+// Initialize the static failure flag.
+SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
+
+// Define custom process command supported by DStubProcess.
+const std::string DStubProcess::stub_proc_command_("cool_proc_cmd");
+
+DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
+ : DProcessBase(name, io_service) {
+};
+
+void
+DStubProcess::init() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_PROCESS_INIT);
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessInit)) {
+ // Simulates a failure to instantiate the process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated init() failure");
+ }
+};
+
+void
+DStubProcess::run() {
+ // Until shut down or an fatal error occurs, wait for and
+ // execute a single callback. This is a preliminary implementation
+ // that is likely to evolve as development progresses.
+ // To use run(), the "managing" layer must issue an io_service::stop
+ // or the call to run will continue to block, and shutdown will not
+ // occur.
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_ENTER);
+ IOServicePtr& io_service = getIoService();
+ while (!shouldShutdown()) {
+ try {
+ io_service->run_one();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, D2PRC_FAILED).arg(ex.what());
+ isc_throw (DProcessBaseError,
+ std::string("Process run method failed:") + ex.what());
+ }
+ }
+
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_RUN_EXIT);
+};
+
+void
+DStubProcess::shutdown() {
+ LOG_DEBUG(d2_logger, DBGLVL_START_SHUT, D2PRC_SHUTDOWN);
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessShutdown)) {
+ // Simulates a failure during shutdown process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated shutdown failure");
+ }
+ setShutdownFlag(true);
+}
+
+isc::data::ConstElementPtr
+DStubProcess::configure(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_CONFIGURE).arg(config_set->str());
+
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
+ // Simulates a process configure failure.
+ return (isc::config::createAnswer(1,
+ "Simulated process configuration error."));
+ }
+
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DStubProcess::command(const std::string& command,
+ isc::data::ConstElementPtr args) {
+ LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
+ D2PRC_COMMAND).arg(command).arg(args->str());
+
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessCommand)) {
+ // Simulates a process command execution failure.
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftProcessCommand");
+ } else if (command.compare(stub_proc_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+DStubProcess::~DStubProcess() {
+};
+
+//************************** DStubController *************************
+
+// Define custom controller command supported by DStubController.
+const std::string DStubController::stub_ctl_command_("spiffy");
+
+// Define custom command line option command supported by DStubController.
+const char* DStubController::stub_option_x_ = "x";
+
+DControllerBasePtr&
+DStubController::instance() {
+ // If the singleton hasn't been created, do it now.
+ if (!getController()) {
+ setController(new DStubController());
+ }
+
+ return (getController());
+}
+
+DStubController::DStubController()
+ : DControllerBase(D2_MODULE_NAME) {
+
+ if (getenv("B10_FROM_BUILD")) {
+ setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/d2/d2.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+bool
+DStubController::customOption(int option, char* /* optarg */)
+{
+ // Check for the custom option supported by DStubController.
+ if ((char)(option) == *stub_option_x_) {
+ return (true);
+ }
+
+ return (false);
+}
+
+DProcessBase* DStubController::createProcess() {
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessException)) {
+ // Simulates a failure to instantiate the process due to exception.
+ throw std::runtime_error("SimFailure::ftCreateProcess");
+ }
+
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessNull)) {
+ // Simulates a failure to instantiate the process.
+ return (NULL);
+ }
+
+ // This should be a successful instantiation.
+ return (new DStubProcess(getName().c_str(), getIOService()));
+}
+
+isc::data::ConstElementPtr
+DStubController::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftControllerCommand)) {
+ // Simulates command failing to execute.
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftControllerCommand");
+ } else if (command.compare(stub_ctl_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+const std::string DStubController::getCustomOpts(){
+ // Return the "list" of custom options supported by DStubController.
+ return (std::string(stub_option_x_));
+}
+
+DStubController::~DStubController() {
+}
+
+// Initialize controller wrapper's static instance getter member.
+DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
new file mode 100644
index 0000000..412b974
--- /dev/null
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -0,0 +1,422 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D_TEST_STUBS_H
+#define D_TEST_STUBS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+
+#include <d2/d_controller.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Class is used to set a globally accessible value that indicates
+/// a specific type of failure to simulate. Test derivations of base classes
+/// can exercise error handling code paths by testing for specific SimFailure
+/// values at the appropriate places and then causing the error to "occur".
+/// The class consists of an enumerated set of failures, and static methods
+/// for getting, setting, and testing the current value.
+class SimFailure {
+public:
+ enum FailureType {
+ ftUnknown = -1,
+ ftNoFailure = 0,
+ ftCreateProcessException,
+ ftCreateProcessNull,
+ ftProcessInit,
+ ftProcessConfigure,
+ ftControllerCommand,
+ ftProcessCommand,
+ ftProcessShutdown
+ };
+
+ /// @brief Sets the SimFailure value to the given value.
+ ///
+ /// @param value is the new value to assign to the global value.
+ static void set(enum FailureType value) {
+ failure_type_ = value;
+ }
+
+ /// @brief Gets the current global SimFailure value
+ ///
+ /// @return returns the current SimFailure value
+ static enum FailureType get() {
+ return (failure_type_);
+ }
+
+ /// @brief One-shot test of the SimFailure value. If the global
+ /// SimFailure value is equal to the given value, clear the global
+ /// value and return true. This makes it convenient for code to
+ /// test and react without having to explicitly clear the global
+ /// value.
+ ///
+ /// @param value is the value against which the global value is
+ /// to be compared.
+ ///
+ /// @return returns true if current SimFailure value matches the
+ /// given value.
+ static bool shouldFailOn(enum FailureType value) {
+ if (failure_type_ == value) {
+ clear();
+ return (true);
+ }
+
+ return (false);
+ }
+
+ static void clear() {
+ failure_type_ = ftNoFailure;
+ }
+
+ static enum FailureType failure_type_;
+};
+
+/// @brief Test Derivation of the DProcessBase class.
+///
+/// This class is used primarily to server as a test process class for testing
+/// DControllerBase. It provides minimal, but sufficient implementation to
+/// test the majority of DControllerBase functionality.
+class DStubProcess : public DProcessBase {
+public:
+
+ /// @brief Static constant that defines a custom process command string.
+ static const std::string stub_proc_command_;
+
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DStubProcess(const char* name, IOServicePtr io_service);
+
+ /// @brief Invoked after process instantiation to perform initialization.
+ /// This implementation supports simulating an error initializing the
+ /// process by throwing a DProcessBaseError if SimFailure is set to
+ /// ftProcessInit.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ /// This implementation is quite basic, surrounding calls to
+ /// io_service->runOne() with a test of the shutdown flag. Once invoked,
+ /// the method will continue until the process itself is exiting due to a
+ /// request to shutdown or some anomaly forces an exit.
+ /// @return returns 0 upon a successful, "normal" termination, non-zero to
+ /// indicate an abnormal termination.
+ virtual void run();
+
+ /// @brief Implements the process shutdown procedure. Currently this is
+ /// limited to setting the instance shutdown flag, which is monitored in
+ /// run().
+ virtual void shutdown();
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This implementation fails if SimFailure is set to ftProcessConfigure.
+ /// Otherwise it will complete successfully. It does not check the content
+ /// of the inbound configuration.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Executes the given command.
+ ///
+ /// This implementation will recognizes one "custom" process command,
+ /// stub_proc_command_. It will fail if SimFailure is set to
+ /// ftProcessCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recognized and executes successfully.
+ /// COMMAND_ERROR if the command is recognized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr command(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ // @brief Destructor
+ virtual ~DStubProcess();
+};
+
+
+/// @brief Test Derivation of the DControllerBase class.
+///
+/// DControllerBase is an abstract class and therefore requires a derivation
+/// for testing. It allows testing the majority of the base class code
+/// without polluting production derivations (e.g. D2Process). It uses
+/// DStubProcess as its application process class. It is a full enough
+/// implementation to support running both stand alone and integrated.
+/// Obviously BIND10 connectivity is not available under unit tests, so
+/// testing here is limited to "failures" to communicate with BIND10.
+class DStubController : public DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns a pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Defines a custom controller command string. This is a
+ /// custom command supported by DStubController.
+ static const std::string stub_ctl_command_;
+
+ /// @brief Defines a custom command line option supported by
+ /// DStubController.
+ static const char* stub_option_x_;
+
+protected:
+ /// @brief Handles additional command line options that are supported
+ /// by DStubController. This implementation supports an option "-x".
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return returns true if the option is "x", otherwise ti returns false.
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Instantiates an instance of DStubProcess.
+ ///
+ /// This implementation will fail if SimFailure is set to
+ /// ftCreateProcessException OR ftCreateProcessNull.
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if SimFailure is set to ftCreateProcessNull.
+ /// @throw throws std::runtime_error if SimFailure is set to
+ /// ftCreateProcessException.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Executes custom controller commands are supported by
+ /// DStubController. This implementation supports one custom controller
+ /// command, stub_ctl_command_. It will fail if SimFailure is set
+ /// to ftControllerCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recognized and executes successfully.
+ /// COMMAND_ERROR if the command is recognized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Provides a string of the additional command line options
+ /// supported by DStubController. DStubController supports one
+ /// addition option, stub_option_x_.
+ ///
+ /// @return returns a string containing the option letters.
+ virtual const std::string getCustomOpts();
+
+private:
+ /// @brief Constructor is private to protect singleton integrity.
+ DStubController();
+
+public:
+ virtual ~DStubController();
+};
+
+/// @brief Abstract Test fixture class that wraps a DControllerBase. This class
+/// is a friend class of DControllerBase which allows it access to class
+/// content to facilitate testing. It provides numerous wrapper methods for
+/// the protected and private methods and member of the base class.
+class DControllerTest : public ::testing::Test {
+public:
+
+ /// @brief Defines a function pointer for controller singleton fetchers.
+ typedef DControllerBasePtr& (*InstanceGetter)();
+
+ /// @brief Static storage of the controller class's singleton fetcher.
+ /// We need this this statically available for callbacks.
+ static InstanceGetter instanceGetter_;
+
+ /// @brief Constructor
+ ///
+ /// @param instance_getter is a function pointer to the static instance
+ /// method of the DControllerBase derivation under test.
+ DControllerTest(InstanceGetter instance_getter) {
+ // Set the static fetcher member, then invoke it via getController.
+ // This ensures the singleton is instantiated.
+ instanceGetter_ = instance_getter;
+ getController();
+ }
+
+ /// @brief Destructor
+ /// Note the controller singleton is destroyed. This is essential to ensure
+ /// a clean start between tests.
+ virtual ~DControllerTest() {
+ getController().reset();
+ }
+
+ /// @brief Convenience method that destructs and then recreates the
+ /// controller singleton under test. This is handy for tests within
+ /// tests.
+ void resetController() {
+ getController().reset();
+ getController();
+ }
+
+ /// @brief Static method which returns the instance of the controller
+ /// under test.
+ /// @return returns a reference to the controller instance.
+ static DControllerBasePtr& getController() {
+ return ((*instanceGetter_)());
+ }
+
+ /// @brief Returns true if the Controller's name matches the given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkName(const std::string& should_be) {
+ return (getController()->getName().compare(should_be) == 0);
+ }
+
+ /// @brief Returns true if the Controller's spec file name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkSpecFileName(const std::string& should_be) {
+ return (getController()->getSpecFileName().compare(should_be) == 0);
+ }
+
+ /// @brief Tests the existence of the Controller's application process.
+ ///
+ /// @return returns true if the process instance exists.
+ bool checkProcess() {
+ return (getController()->process_);
+ }
+
+ /// @brief Tests the existence of the Controller's IOService.
+ ///
+ /// @return returns true if the IOService exists.
+ bool checkIOService() {
+ return (getController()->io_service_);
+ }
+
+ /// @brief Gets the Controller's IOService.
+ ///
+ /// @return returns a reference to the IOService
+ IOServicePtr& getIOService() {
+ return (getController()->io_service_);
+ }
+
+ /// @brief Compares stand alone flag with the given value.
+ ///
+ /// @param value
+ ///
+ /// @return returns true if the stand alone flag is equal to the given
+ /// value.
+ bool checkStandAlone(bool value) {
+ return (getController()->isStandAlone() == value);
+ }
+
+ /// @brief Sets the controller's stand alone flag to the given value.
+ ///
+ /// @param value is the new value to assign.
+ ///
+ void setStandAlone(bool value) {
+ getController()->setStandAlone(value);
+ }
+
+ /// @brief Compares verbose flag with the given value.
+ ///
+ /// @param value
+ ///
+ /// @return returns true if the verbose flag is equal to the given value.
+ bool checkVerbose(bool value) {
+ return (getController()->isVerbose() == value);
+ }
+
+ /// @Wrapper to invoke the Controller's parseArgs method. Please refer to
+ /// DControllerBase::parseArgs for details.
+ void parseArgs(int argc, char* argv[]) {
+ getController()->parseArgs(argc, argv);
+ }
+
+ /// @Wrapper to invoke the Controller's init method. Please refer to
+ /// DControllerBase::init for details.
+ void initProcess() {
+ getController()->initProcess();
+ }
+
+ /// @Wrapper to invoke the Controller's establishSession method. Please
+ /// refer to DControllerBase::establishSession for details.
+ void establishSession() {
+ getController()->establishSession();
+ }
+
+ /// @Wrapper to invoke the Controller's launch method. Please refer to
+ /// DControllerBase::launch for details.
+ int launch(int argc, char* argv[]) {
+ optind = 1;
+ return (getController()->launch(argc, argv));
+ }
+
+ /// @Wrapper to invoke the Controller's disconnectSession method. Please
+ /// refer to DControllerBase::disconnectSession for details.
+ void disconnectSession() {
+ getController()->disconnectSession();
+ }
+
+ /// @Wrapper to invoke the Controller's updateConfig method. Please
+ /// refer to DControllerBase::updateConfig for details.
+ isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->updateConfig(new_config));
+ }
+
+ /// @Wrapper to invoke the Controller's executeCommand method. Please
+ /// refer to DControllerBase::executeCommand for details.
+ isc::data::ConstElementPtr executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args){
+ return (getController()->executeCommand(command, args));
+ }
+
+ /// @brief Callback that will generate shutdown command via the
+ /// command callback function.
+ static void genShutdownCallback() {
+ isc::data::ElementPtr arg_set;
+ DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ }
+
+ /// @brief Callback that throws an exception.
+ static void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 6f119ed..01e7da7 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -57,12 +57,18 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
+ const bool direct_response_desired) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
- // it may throw something if things go wrong
- IfaceMgr::instance();
+ // it may throw something if things go wrong.
+ // The 'true' value of the call to setMatchingPacketFilter imposes
+ // that IfaceMgr will try to use the mechanism to respond directly
+ // to the client which doesn't have address assigned. This capability
+ // may be lacking on some OSes, so there is no guarantee that server
+ // will be able to respond directly.
+ IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
if (port) {
// open sockets only if port is non-zero. Port 0 is used
@@ -199,9 +205,9 @@ Dhcpv4Srv::run() {
}
if (rsp) {
- if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
- rsp->setRemoteAddr(query->getRemoteAddr());
- }
+
+ adjustRemoteAddr(query, rsp);
+
if (!rsp->getHops()) {
rsp->setRemotePort(DHCP4_CLIENT_PORT);
} else {
@@ -352,19 +358,27 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// relay address
answer->setGiaddr(question->getGiaddr());
- if (question->getGiaddr().toText() != "0.0.0.0") {
- // relayed traffic
- answer->setRemoteAddr(question->getGiaddr());
- } else {
- // direct traffic
- answer->setRemoteAddr(question->getRemoteAddr());
- }
-
// Let's copy client-id to response. See RFC6842.
OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (client_id) {
answer->addOption(client_id);
}
+
+ // If src/dest HW addresses are used by the packet filtering class
+ // we need to copy them as well. There is a need to check that the
+ // address being set is not-NULL because an attempt to set the NULL
+ // HW would result in exception. If these values are not set, the
+ // the default HW addresses (zeroed) should be generated by the
+ // packet filtering class when creating Ethernet header for
+ // outgoing packet.
+ HWAddrPtr src_hw_addr = question->getLocalHWAddr();
+ if (src_hw_addr) {
+ answer->setLocalHWAddr(src_hw_addr);
+ }
+ HWAddrPtr dst_hw_addr = question->getRemoteHWAddr();
+ if (dst_hw_addr) {
+ answer->setRemoteHWAddr(dst_hw_addr);
+ }
}
void
@@ -517,28 +531,6 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setYiaddr(lease->addr_);
- // If remote address is not set, we are dealing with a directly
- // connected client requesting new lease. We can send response to
- // the address assigned in the lease, but first we have to make sure
- // that IfaceMgr supports responding directly to the client when
- // client doesn't have address assigned to its interface yet.
- if (answer->getRemoteAddr().toText() == "0.0.0.0") {
- if (IfaceMgr::instance().isDirectResponseSupported()) {
- answer->setRemoteAddr(lease->addr_);
- } else {
- // Since IfaceMgr does not support direct responses to
- // clients not having IP addresses, we have to send response
- // to broadcast. We don't check whether the use_bcast flag
- // was set in the constructor, because this flag is only used
- // by unit tests to prevent opening broadcast sockets, as
- // it requires root privileges. If this function is invoked by
- // unit tests, we expect that it sets broadcast address if
- // direct response is not supported, so as a test can verify
- // function's behavior, regardless of the use_bcast flag's value.
- answer->setRemoteAddr(IOAddress("255.255.255.255"));
- }
- }
-
// IP Address Lease time (type 51)
opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
opt->setUint32(lease->valid_lft_);
@@ -573,6 +565,60 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
}
}
+void
+Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
+ // Let's create static objects representing zeroed and broadcast
+ // addresses. We will use them further in this function to test
+ // other addresses against them. Since they are static, they will
+ // be created only once.
+ static const IOAddress zero_addr("0.0.0.0");
+ static const IOAddress bcast_addr("255.255.255.255");
+
+ // If received relayed message, server responds to the relay address.
+ if (question->getGiaddr() != zero_addr) {
+ msg->setRemoteAddr(question->getGiaddr());
+
+ // If giaddr is 0 but client set ciaddr, server should unicast the
+ // response to ciaddr.
+ } else if (question->getCiaddr() != zero_addr) {
+ msg->setRemoteAddr(question->getCiaddr());
+
+ // We can't unicast the response to the client when sending NAK,
+ // because we haven't allocated address for him. Therefore,
+ // NAK is broadcast.
+ } else if (msg->getType() == DHCPNAK) {
+ msg->setRemoteAddr(bcast_addr);
+
+ // If yiaddr is set it means that we have created a lease for a client.
+ } else if (msg->getYiaddr() != zero_addr) {
+ // If the broadcast bit is set in the flags field, we have to
+ // send the response to broadcast address. Client may have requested it
+ // because it doesn't support reception of messages on the interface
+ // which doesn't have an address assigned. The other case when response
+ // must be broadcasted is when our server does not support responding
+ // directly to a client without address assigned.
+ const bool bcast_flag = ((question->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
+ if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
+ msg->setRemoteAddr(bcast_addr);
+
+ // Client cleared the broadcast bit and we support direct responses
+ // so we should unicast the response to a newly allocated address -
+ // yiaddr.
+ } else {
+ msg->setRemoteAddr(msg->getYiaddr());
+
+ }
+
+ // In most cases, we should have the remote address found already. If we
+ // found ourselves at this point, the rational thing to do is to respond
+ // to the address we got the query from.
+ } else {
+ msg->setRemoteAddr(question->getRemoteAddr());
+
+ }
+}
+
+
OptionPtr
Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
uint32_t netmask = getNetmask4(subnet->get().second);
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index bc8851e..f98233c 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -45,7 +45,7 @@ namespace dhcp {
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
class Dhcpv4Srv : public boost::noncopyable {
- public:
+public:
/// @brief defines if certain option may, must or must not appear
typedef enum {
@@ -61,15 +61,22 @@ class Dhcpv4Srv : public boost::noncopyable {
/// network interaction. Will instantiate lease manager, and load
/// old or create new DUID. It is possible to specify alternate
/// port on which DHCPv4 server will listen on. That is mostly useful
- /// for testing purposes.
+ /// for testing purposes. The Last two arguments of the constructor
+ /// should be left at default values for normal server operation.
+ /// They should be set to 'false' when creating an instance of this
+ /// class for unit testing because features they enable require
+ /// root privileges.
///
/// @param port specifies port number to listen on
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
/// @param use_bcast configure sockets to support broadcast messages.
+ /// @param direct_response_desired specifies if it is desired to
+ /// use direct V4 traffic.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
const char* dbconfig = "type=memfile",
- const bool use_bcast = true);
+ const bool use_bcast = true,
+ const bool direct_response_desired = true);
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -218,6 +225,23 @@ protected:
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
+ /// @brief Sets remote addresses for outgoing packet.
+ ///
+ /// This method sets the local and remote addresses on outgoing packet.
+ /// The addresses being set depend on the following conditions:
+ /// - has incoming packet been relayed,
+ /// - is direct response to a client without address supported,
+ /// - type of the outgoing packet,
+ /// - broadcast flag set in the incoming packet.
+ ///
+ /// @warning This method does not check whether provided packet pointers
+ /// are valid. Make sure that pointers are correct before calling this
+ /// function.
+ ///
+ /// @param question instance of a packet received by a server.
+ /// @param [out] msg response packet which addresses are to be adjusted.
+ void adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg);
+
/// @brief Returns server-identifier option
///
/// @return server-id option
@@ -272,7 +296,7 @@ protected:
/// initiate server shutdown procedure.
volatile bool shutdown_;
- private:
+private:
/// @brief Constructs netmask option based on subnet4
/// @param subnet subnet for which the netmask will be calculated
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index cabd8b7..c39c56a 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -22,6 +22,8 @@
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcpsrv/cfgmgr.h>
@@ -50,14 +52,34 @@ public:
/// @brief Constructor.
///
- /// It disables configuration of broadcast options on
- /// sockets that are opened by the Dhcpv4Srv constructor.
- /// Setting broadcast options requires root privileges
- /// which is not the case when running unit tests.
+ /// This constructor disables default modes of operation used by the
+ /// Dhcpv4Srv class:
+ /// - Send/receive broadcast messages through sockets on interfaces
+ /// which support broadcast traffic.
+ /// - Direct DHCPv4 traffic - communication with clients which do not
+ /// have IP address assigned yet.
+ ///
+ /// Enabling these modes requires root privilges so they must be
+ /// disabled for unit testing.
+ ///
+ /// Note, that disabling broadcast options on sockets does not impact
+ /// the operation of these tests because they use local loopback
+ /// interface which doesn't have broadcast capability anyway. It rather
+ /// prevents setting broadcast options on other (broadcast capable)
+ /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+ ///
+ /// The Direct DHCPv4 Traffic capability can be disabled here because
+ /// it is tested with PktFilterLPFTest unittest. The tests which belong
+ /// to PktFilterLPFTest can be enabled on demand when root privileges can
+ /// be guaranteed.
+ ///
+ /// @param port port number to listen on; the default value 0 indicates
+ /// that sockets should not be opened.
NakedDhcpv4Srv(uint16_t port = 0)
- : Dhcpv4Srv(port, "type=memfile", false) {
+ : Dhcpv4Srv(port, "type=memfile", false, false) {
}
+ using Dhcpv4Srv::adjustRemoteAddr;
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
using Dhcpv4Srv::processRelease;
@@ -73,6 +95,41 @@ public:
static const char* SRVID_FILE = "server-id-test.txt";
+/// @brief Dummy Packet Filtering class.
+///
+/// This class reports capability to respond directly to the
+/// client which doesn't have address configured yet.
+///
+/// All packet and socket handling functions do nothing because
+/// they are not used in unit tests.
+class PktFilterTest : public PktFilter {
+public:
+
+ /// @brief Reports 'direct response' capability.
+ ///
+ /// @return always true.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
+ /// Does nothing.
+ virtual int openSocket(const Iface&, const IOAddress&, const uint16_t,
+ const bool, const bool) {
+ return (0);
+ }
+
+ /// Does nothing.
+ virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) {
+ return Pkt4Ptr();
+ }
+
+ /// Does nothing.
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+};
+
class Dhcpv4SrvTest : public ::testing::Test {
public:
@@ -171,6 +228,11 @@ public:
EXPECT_EQ(q->getIface(), a->getIface());
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
+ // When processing an incoming packet the remote address
+ // is copied as a src address, and the source address is
+ // copied as a remote address to the response.
+ EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
+ EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
// Check that bare minimum of required options are there.
// We don't check options requested by a client. Those
@@ -360,26 +422,43 @@ public:
/// @brief Tests if Discover or Request message is processed correctly
///
/// @param msg_type DHCPDISCOVER or DHCPREQUEST
- /// @param client_addr client address
- /// @param relay_addr relay address
- void testDiscoverRequest(const uint8_t msg_type,
- const IOAddress& client_addr,
- const IOAddress& relay_addr) {
-
+ void testDiscoverRequest(const uint8_t msg_type) {
+ // Create an instance of the tested class.
boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Initialize the source HW address.
vector<uint8_t> mac(6);
- for (int i = 0; i < 6; i++) {
- mac[i] = i*10;
+ for (int i = 0; i < 6; ++i) {
+ mac[i] = i * 10;
}
-
+ // Initialized the destination HW address.
+ vector<uint8_t> dst_mac(6);
+ for (int i = 0; i < 6; ++i) {
+ dst_mac[i] = i * 20;
+ }
+ // Create a DHCP message. It will be used to simulate the
+ // incoming message.
boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
+ // Create a response message. It will hold a reponse packet.
+ // Initially, set it to NULL.
boost::shared_ptr<Pkt4> rsp;
-
+ // Set the name of the interface on which packet is received.
req->setIface("eth0");
+ // Set the interface index. It is just a dummy value and will
+ // not be interpreted.
req->setIndex(17);
+ // Set the target HW address. This value is normally used to
+ // construct the data link layer header.
+ req->setRemoteHWAddr(1, 6, dst_mac);
+ // Set the HW address. This value is set on DHCP level (in chaddr).
req->setHWAddr(1, 6, mac);
- req->setRemoteAddr(IOAddress(client_addr));
- req->setGiaddr(relay_addr);
+ // Set local HW address. It is used to construct the data link layer
+ // header.
+ req->setLocalHWAddr(1, 6, mac);
+ // Set target IP address.
+ req->setRemoteAddr(IOAddress("192.0.2.55"));
+ // Set relay address.
+ req->setGiaddr(IOAddress("192.0.2.10"));
// We are going to test that certain options are returned
// in the response message when requested using 'Parameter
@@ -407,33 +486,6 @@ public:
}
- if (relay_addr.toText() != "0.0.0.0") {
- // This is relayed message. It should be sent brsp to relay address.
- EXPECT_EQ(req->getGiaddr().toText(),
- rsp->getRemoteAddr().toText());
-
- } else if (client_addr.toText() != "0.0.0.0") {
- // This is a message from a client having an IP address.
- EXPECT_EQ(req->getRemoteAddr().toText(),
- rsp->getRemoteAddr().toText());
-
- } else {
- // This is a message from a client having no IP address yet.
- // If IfaceMgr supports direct traffic the response should
- // be sent to the new address assigned to the client.
- if (IfaceMgr::instance().isDirectResponseSupported()) {
- EXPECT_EQ(rsp->getYiaddr(),
- rsp->getRemoteAddr().toText());
-
- // If direct response to the client having no IP address is
- // not supported, response should go to broadcast.
- } else {
- EXPECT_EQ("255.255.255.255", rsp->getRemoteAddr().toText());
-
- }
-
- }
-
messageCheck(req, rsp);
// We did not request any options so these should not be present
@@ -471,12 +523,35 @@ public:
}
- ~Dhcpv4SrvTest() {
+ /// @brief This function cleans up after the test.
+ virtual void TearDown() {
+
CfgMgr::instance().deleteSubnets4();
// Let's clean up if there is such a file.
unlink(SRVID_FILE);
- };
+
+ // Close all open sockets.
+ IfaceMgr::instance().closeSockets();
+
+ // Some unit tests override the default packet filtering class, used
+ // by the IfaceMgr. The dummy class, called PktFilterTest, reports the
+ // capability to directly respond to the clients without IP address
+ // assigned. This capability is not supported by the default packet
+ // filtering class: PktFilterInet. Therefore setting the dummy class
+ // allows to test scenarios, when server responds to the broadcast address
+ // on client's request, despite having support for direct response.
+ // The following call restores the use of original packet filtering class
+ // after the test.
+ try {
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ } catch (const Exception& ex) {
+ FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
+ << " class after the test. Exception has been caught: "
+ << ex.what();
+ }
+ }
/// @brief A subnet used in most tests
Subnet4Ptr subnet_;
@@ -494,20 +569,220 @@ TEST_F(Dhcpv4SrvTest, basic) {
// Check that the base class can be instantiated
boost::scoped_ptr<Dhcpv4Srv> srv;
- ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000, "type=memfile",
+ false, false)));
srv.reset();
+ // We have to close open sockets because further in this test we will
+ // call the Dhcpv4Srv constructor again. This constructor will try to
+ // set the appropriate packet filter class for IfaceMgr. This requires
+ // that all sockets are closed.
+ IfaceMgr::instance().closeSockets();
// Check that the derived class can be instantiated
boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
ASSERT_NO_THROW(
- naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
EXPECT_TRUE(naked_srv->getServerID());
+ // Close sockets again for the next test.
+ IfaceMgr::instance().closeSockets();
ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
EXPECT_TRUE(naked_srv->getServerID());
}
-// Verifies that DISCOVER received via relay can be processed correctly,
+// This test verifies that the destination address of the response
+// message is set to giaddr, when giaddr is set to non-zero address
+// in the received message.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRelay) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.2.1"));
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Create a response packet. Assume that the new lease have
+ // been created and new address allocated. This address is
+ // stored in yiaddr field.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ resp->setYiaddr(IOAddress("192.0.2.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Now the destination address should be relay's address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+
+ // Let's do another test and set other fields: ciaddr and
+ // flags. By doing it, we want to make sure that the relay
+ // address will take precedence.
+ req->setGiaddr(IOAddress("192.0.2.50"));
+ req->setCiaddr(IOAddress("192.0.2.11"));
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ resp->setYiaddr(IOAddress("192.0.2.100"));
+ // Clear remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Response should be sent back to the relay address.
+ EXPECT_EQ("192.0.2.50", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to ciaddr when giaddr is set to zero and the ciaddr is set to
+// non-zero address in the received message. This is the case when the
+// client is in Renew or Rebind state.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRenewRebind) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Set ciaddr to non-zero address. The response should be sent to this
+ // address as the client is in renewing or rebinding state (it is fully
+ // configured).
+ req->setCiaddr(IOAddress("192.0.2.15"));
+ // Let's configure broadcast flag. It should be ignored because
+ // we are responding directly to the client having an address
+ // and trying to extend his lease. Broadcast flag is only used
+ // when new lease is acquired and server must make a decision
+ // whether to unicast the response to the acquired address or
+ // broadcast it.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Let's extend the lease for the client in such a way that
+ // it will actually get different address. The response
+ // should not be sent to this address but rather to ciaddr
+ // as client still have ciaddr configured.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Check that server responds to ciaddr
+ EXPECT_EQ("192.0.2.15", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set correctly when giaddr and ciaddr is zeroed in the received message
+// and the new lease is acquired. The lease address is carried in the
+// response message in the yiaddr field. In this case destination address
+// of the response should be set to yiaddr if server supports direct responses
+// to the client which doesn't have an address yet or broadcast if the server
+// doesn't support direct responses.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressSelect) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's clear the broadcast flag.
+ req->setFlags(0);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // When running unit tests, the IfaceMgr is using the default Packet
+ // Filtering class, PktFilterInet. This class does not support direct
+ // responses to clients without address assigned. When giaddr and ciaddr
+ // are zero and client has just got new lease, the assigned address is
+ // carried in yiaddr. In order to send this address to the client,
+ // server must broadcast its response.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Check that the response is sent to broadcast address as the
+ // server doesn't have capability to respond directly.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+ // We also want to test the case when the server has capability to
+ // respond directly to the client which is not configured. Server
+ // makes decision whether it responds directly or broadcast its
+ // response based on the capability reported by IfaceMgr. In order
+ // to set this capability we have to provide a dummy Packet Filter
+ // class which would report the support for direct responses.
+ // This class is called PktFilterTest.
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+ // Now we expect that the server will send its response to the
+ // address assigned for the client.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ EXPECT_EQ("192.0.2.13", resp->getRemoteAddr().toText());
+}
+
+// This test verifies that the destination address of the response message
+// is set to broadcast address when client set broadcast flag in its
+// query. Client sets this flag to indicate that it can't receive direct
+// responses from the server when it doesn't have its interface configured.
+// Server must respect broadcast flag.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressBroadcast) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's set the broadcast flag.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // When running unit tests, the IfaceMgr is using the default Packet
+ // Filtering class, PktFilterInet. This class does not support direct
+ // responses to the clients without address assigned. If giaddr and
+ // ciaddr are zero and client has just got the new lease, the assigned
+ // address is carried in yiaddr. In order to send this address to the
+ // client, server must send the response to the broadcast address when
+ // direct response is not supported. This conflicts with the purpose
+ // of this test which is supposed to verify that responses are sent
+ // to broadcast address only, when broadcast flag is set. Therefore,
+ // in order to simulate that direct responses are supported we have
+ // to replace the default packet filtering class with a dummy class
+ // which reports direct response capability.
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Server must repond to broadcast address when client desired that
+ // by setting the broadcast flag in its request.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+}
+
+// Verifies that DISCOVER message can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
@@ -515,29 +790,11 @@ TEST_F(Dhcpv4SrvTest, basic) {
// are other tests that verify correctness of the allocation
// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
// and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
- testDiscoverRequest(DHCPDISCOVER,
- IOAddress("192.0.2.56"),
- IOAddress("192.0.2.67"));
-}
-
-// Verifies that the non-relayed DISCOVER is processed correctly when
-// client source address is specified.
-TEST_F(Dhcpv4SrvTest, processDiscoverNoRelay) {
- testDiscoverRequest(DHCPDISCOVER,
- IOAddress("0.0.0.0"),
- IOAddress("192.0.2.67"));
+TEST_F(Dhcpv4SrvTest, processDiscover) {
+ testDiscoverRequest(DHCPDISCOVER);
}
-// Verified that the non-relayed DISCOVER is processed correctly when
-// client source address is not specified.
-TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
- testDiscoverRequest(DHCPDISCOVER,
- IOAddress("0.0.0.0"),
- IOAddress("0.0.0.0"));
-}
-
-// Verifies that REQUEST received via relay can be processed correctly,
+// Verifies that REQUEST message can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
@@ -545,26 +802,8 @@ TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
// are other tests that verify correctness of the allocation
// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
// and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processRequestRelay) {
- testDiscoverRequest(DHCPREQUEST,
- IOAddress("192.0.2.56"),
- IOAddress("192.0.2.67"));
-}
-
-// Verifies that the non-relayed REQUEST is processed correctly when
-// client source address is specified.
-TEST_F(Dhcpv4SrvTest, processRequestNoRelay) {
- testDiscoverRequest(DHCPREQUEST,
- IOAddress("0.0.0.0"),
- IOAddress("192.0.2.67"));
-}
-
-// Verified that the non-relayed REQUEST is processed correctly when
-// client source address is not specified.
-TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
- testDiscoverRequest(DHCPREQUEST,
- IOAddress("0.0.0.0"),
- IOAddress("0.0.0.0"));
+TEST_F(Dhcpv4SrvTest, processRequest) {
+ testDiscoverRequest(DHCPREQUEST);
}
TEST_F(Dhcpv4SrvTest, processRelease) {
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 86f4d8f..4f5133c 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -463,10 +463,9 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
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));
+ // the OptionCustom class is used here instead.
+ OptionCustomPtr option_status =
+ OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
assert(option_status);
// Set status code to 'code' (0 - means data field #0).
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.py.in b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
index 37be304..b0fd30f 100755
--- a/src/bin/usermgr/b10-cmdctl-usermgr.py.in
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
@@ -75,7 +75,7 @@ class UserManager:
# Just let any file read error bubble up; it will
# be caught in the run() method
with open(self.options.output_file, newline='') as csvfile:
- reader = csv.reader(csvfile)
+ reader = csv.reader(csvfile, strict=True)
for row in reader:
self.user_info[row[0]] = row
diff --git a/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
index 3d0d4af..0766b7c 100644
--- a/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
+++ b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
@@ -133,6 +133,8 @@ class TestUserMgr(unittest.TestCase):
May be None, in which case the check is skipped.
expected_stderr, (multiline) string that is checked against stderr.
May be None, in which case the check is skipped.
+
+ Returns the standard output and error captured to a string.
"""
(returncode, stdout, stderr) = run(command)
if expected_stderr is not None:
@@ -140,6 +142,7 @@ class TestUserMgr(unittest.TestCase):
if expected_stdout is not None:
self.assertEqual(expected_stdout, stdout.decode())
self.assertEqual(expected_returncode, returncode, " ".join(command))
+ return (stdout.decode(), stderr.decode())
def test_help(self):
self.run_check(0,
@@ -468,14 +471,26 @@ Options:
# I can only think of one invalid format, an unclosed string
with open(self.OUTPUT_FILE, 'w', newline='') as f:
f.write('a,"\n')
- self.run_check(2,
- 'Using accounts file: test_users.csv\n'
- 'Error parsing csv file: newline inside string\n',
+ # Different versions of the csv library return different errors.
+ # So we need to check the output in a little more complex way.
+ # We ask the run_check not to check the output and check it
+ # ourselves.
+ (stdout, stderr) = self.run_check(2, None,
'',
[ self.TOOL,
'-f', self.OUTPUT_FILE,
'add', 'user1', 'pass1'
])
+ # This looks little bit awkward, but is probably easiest with
+ # just 2 known possibilities. If there are more, we'll have to
+ # think of something else.
+ self.assertTrue(stdout ==
+ 'Using accounts file: test_users.csv\n'
+ 'Error parsing csv file: newline inside string\n' or
+ stdout ==
+ 'Using accounts file: test_users.csv\n'
+ 'Error parsing csv file: unexpected end of data\n')
+
if __name__== '__main__':
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index f663e55..7c71e78 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -18,6 +18,8 @@
import unittest
import os
+import socket
+import fcntl
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.cc.session import *
@@ -190,6 +192,29 @@ class Dbserver:
def decrease_transfers_counter(self):
self.transfer_counter -= 1
+class TestUtility(unittest.TestCase):
+ """Test some utility functions."""
+
+ def test_make_blockign(self):
+ def is_blocking(fd):
+ return (fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK) == 0
+
+ # socket.socket doesn't support the 'with' statement before Python
+ # 3.2, so we'll close it ourselves (while it's not completely exception
+ # safe, it should be acceptable for a test case)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM,
+ socket.IPPROTO_TCP)
+ # By default socket is made blocking
+ self.assertTrue(is_blocking(sock.fileno()))
+ # make_blocking(False) makes it non blocking
+ xfrout.make_blocking(sock.fileno(), False)
+ self.assertFalse(is_blocking(sock.fileno()))
+ # make_blocking(True) makes it blocking again
+ xfrout.make_blocking(sock.fileno(), True)
+ self.assertTrue(is_blocking(sock.fileno()))
+
+ sock.close()
+
class TestXfroutSessionBase(unittest.TestCase):
'''Base class for tests related to xfrout sessions
@@ -269,6 +294,12 @@ class TestXfroutSessionBase(unittest.TestCase):
self.xfrsess._request_typestr = 'IXFR'
def setUp(self):
+ # xfrout.make_blocking won't work with faked socket, and we'd like to
+ # examine how it's called, so we replace it with our mock.
+ self.__orig_make_blocking = xfrout.make_blocking
+ self._make_blocking_history = []
+ xfrout.make_blocking = lambda x, y: self.__make_blocking(x, y)
+
self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
TSIGKeyRing(),
@@ -285,7 +316,11 @@ class TestXfroutSessionBase(unittest.TestCase):
# original is used elsewhere.
self.orig_get_rrset_len = xfrout.get_rrset_len
+ def __make_blocking(self, fd, on):
+ self._make_blocking_history.append((fd, on))
+
def tearDown(self):
+ xfrout.make_blocking = self.__orig_make_blocking
xfrout.get_rrset_len = self.orig_get_rrset_len
# transfer_counter must be always be reset no matter happens within
# the XfroutSession object. We check the condition here.
@@ -306,7 +341,12 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_quota_ok(self):
'''The default case in terms of the xfrout quota.
+ It also confirms that the socket is made blocking.
+
'''
+ # make_blocking shouldn't be called yet.
+ self.assertEqual([], self._make_blocking_history)
+
# set up a bogus request, which should result in FORMERR. (it only
# has to be something that is different from the previous case)
self.xfrsess._request_data = \
@@ -316,6 +356,26 @@ class TestXfroutSession(TestXfroutSessionBase):
XfroutSession._handle(self.xfrsess)
self.assertEqual(self.sock.read_msg().get_rcode(), Rcode.FORMERR)
+ # make_blocking should have been called in _handle(). Note that
+ # in the test fixture we handle fileno as a faked socket object, not
+ # as integer.
+ self.assertEqual([(self.sock, True)], self._make_blocking_history)
+
+ def test_make_blocking_fail(self):
+ """Check what if make_blocking() raises an exception."""
+
+ # make_blocking() can fail due to OSError. It shouldn't cause
+ # disruption, and xfrout_start shouldn't be called.
+
+ def raise_exception():
+ raise OSError('faked error')
+ xfrout.make_blocking = lambda x, y: raise_exception()
+ self.start_arg = []
+ self.xfrsess.dns_xfrout_start = \
+ lambda s, x, y: self.start_arg.append((x, y))
+ XfroutSession._handle(self.xfrsess)
+ self.assertEqual([], self.start_arg)
+
def test_exception_from_session(self):
'''Test the case where the main processing raises an exception.
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index ab6d091..0055bb1 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -30,6 +30,7 @@ from isc.cc import SessionError, SessionTimeout
from isc.statistics import Counters
from isc.notify import notify_out
import isc.util.process
+import fcntl
import socket
import select
import errno
@@ -152,13 +153,26 @@ def get_soa_serial(soa_rdata):
'''
return Serial(int(soa_rdata.to_text().split()[2]))
-def make_blocking(fd, on=True):
- flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+def make_blocking(filenum, on):
+ """A helper function to change blocking mode of the given socket.
+
+ It sets the mode of blocking I/O for the socket associated with filenum
+ (descriptor of the socket) according to parameter 'on': if it's True the
+ file will be made blocking; otherwise it will be made non-blocking.
+
+ The given filenum must be a descriptor of a socket (not an ordinary file
+ etc), but this function doesn't check that condition.
+
+ filenum(int): file number (descriptor) of the socket to update.
+ on(bool): whether enable (True) or disable (False) blocking I/O.
+
+ """
+ flags = fcntl.fcntl(filenum, fcntl.F_GETFL)
if on: # make it blocking
flags &= ~os.O_NONBLOCK
else: # make it non blocking
flags |= os.O_NONBLOCK
- fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+ fcntl.fcntl(filenum, fcntl.F_SETFL, flags)
class XfroutSession():
def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
@@ -198,6 +212,10 @@ class XfroutSession():
quota_ok = self._server.increase_transfers_counter()
ex = None
try:
+ # Before start, make sure the socket uses blocking I/O because
+ # responses will be sent in the blocking mode; otherwise it could
+ # result in EWOULDBLOCK and disrupt the session.
+ make_blocking(self._sock_fd, True)
self.dns_xfrout_start(self._sock_fd, self._request_data, quota_ok)
except Exception as e:
# To avoid resource leak we need catch all possible exceptions
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index 5f2a264..71aded7 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -103,6 +103,12 @@ would be better to not be too verbose, but you might want to increase
the log level and make sure there's no resource leak or other system
level troubles when it's logged.
+% ASIODNS_TCP_CLOSE_NORESP_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client without returning an answer (which normally happens for zone
+transfer requests), but it failed to do that. See ASIODNS_TCP_CLOSE_FAIL
+for more details.
+
% ASIODNS_TCP_GETREMOTE_FAIL failed to get remote address of a DNS TCP connection: %1
A TCP DNS server tried to get the address and port of a remote client
on a connected socket but failed. It's expected to be rare but can
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index 0324980..972e397 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -64,8 +64,9 @@ public:
// SyncUDPServer has different constructor signature so it cannot be
// templated.
void addSyncUDPServerFromFD(int fd, int af) {
- SyncUDPServerPtr server(new SyncUDPServer(io_service_.get_io_service(),
- fd, af, lookup_));
+ SyncUDPServerPtr server(SyncUDPServer::create(
+ io_service_.get_io_service(), fd, af,
+ lookup_));
startServer(server);
}
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index 989484f..d3e30af 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -26,6 +26,8 @@
#include <boost/bind.hpp>
+#include <cassert>
+
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
@@ -38,13 +40,19 @@ using namespace isc::asiolink;
namespace isc {
namespace asiodns {
+SyncUDPServerPtr
+SyncUDPServer::create(asio::io_service& io_service, const int fd,
+ const int af, DNSLookup* lookup)
+{
+ return (SyncUDPServerPtr(new SyncUDPServer(io_service, fd, af, lookup)));
+}
+
SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
const int af, DNSLookup* lookup) :
output_buffer_(new isc::util::OutputBuffer(0)),
query_(new isc::dns::Message(isc::dns::Message::PARSE)),
udp_endpoint_(sender_), lookup_callback_(lookup),
- resume_called_(false), done_(false), stopped_(false),
- recv_callback_(boost::bind(&SyncUDPServer::handleRead, this, _1, _2))
+ resume_called_(false), done_(false), stopped_(false)
{
if (af != AF_INET && af != AF_INET6) {
isc_throw(InvalidParameter, "Address family must be either AF_INET "
@@ -69,19 +77,18 @@ SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
void
SyncUDPServer::scheduleRead() {
- socket_->async_receive_from(asio::mutable_buffers_1(data_, MAX_LENGTH),
- sender_, recv_callback_);
+ socket_->async_receive_from(
+ asio::mutable_buffers_1(data_, MAX_LENGTH), sender_,
+ boost::bind(&SyncUDPServer::handleRead, shared_from_this(), _1, _2));
}
void
SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
- // If the server has been stopped, it could even have been destroyed
- // by the time of this call. We'll solve this problem in #2946, but
- // until then we exit as soon as possible without accessing any other
- // invalidated fields (note that referencing stopped_ is also incorrect,
- // but experiments showed it often keeps the original value in practice,
- // so we live with it until the complete fix).
if (stopped_) {
+ // stopped_ can be set to true only after the socket object is closed.
+ // checking this would also detect premature destruction of 'this'
+ // object.
+ assert(socket_ && !socket_->is_open());
return;
}
if (ec) {
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
index cabc8bb..e7315be 100644
--- a/src/lib/asiodns/sync_udp_server.h
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -33,23 +33,43 @@
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
#include <stdint.h>
namespace isc {
namespace asiodns {
+class SyncUDPServer;
+typedef boost::shared_ptr<SyncUDPServer> SyncUDPServerPtr;
+
/// \brief An UDP server that doesn't asynchronous lookup handlers.
///
/// That means, the lookup handler must provide the answer right away.
/// This allows for implementation with less overhead, compared with
/// the \c UDPServer class.
-class SyncUDPServer : public DNSServer, public boost::noncopyable {
+///
+/// This class inherits from boost::enable_shared_from_this so a shared
+/// pointer of this object can be passed in an ASIO callback and won't be
+/// accidentally destroyed while waiting for events. To enforce this style
+/// of creation, a static factory method is provided, and the constructor is
+/// hidden as a private.
+class SyncUDPServer : public DNSServer,
+ public boost::enable_shared_from_this<SyncUDPServer>,
+ boost::noncopyable
+{
+private:
+ /// \brief Constructor.
+ ///
+ /// This is hidden as private (see the class description).
+ SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
+ DNSLookup* lookup);
+
public:
- /// \brief Constructor
+ /// \brief Factory of SyncUDPServer object in the form of shared_ptr.
///
/// Due to the nature of this server, it's meaningless if the lookup
- /// callback is NULL. So the constructor explicitly rejects that case
+ /// callback is NULL. So this method explicitly rejects that case
/// with an exception. Likewise, it doesn't take "checkin" or "answer"
/// callbacks. In fact, calling "checkin" from receive callback does not
/// make sense for any of the DNSServer variants (see Trac #2935);
@@ -67,8 +87,8 @@ public:
/// \throw isc::InvalidParameter lookup is NULL
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor.
- SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
- DNSLookup* lookup);
+ static SyncUDPServerPtr create(asio::io_service& io_service, const int fd,
+ const int af, DNSLookup* lookup);
/// \brief Start the SyncUDPServer.
///
@@ -133,7 +153,7 @@ private:
// requires it.
isc::dns::MessagePtr query_, answer_;
// The socket used for the communication
- std::auto_ptr<asio::ip::udp::socket> socket_;
+ boost::scoped_ptr<asio::ip::udp::socket> socket_;
// Wrapper of socket_ in the form of asiolink::IOSocket.
// "DummyIOCallback" is not necessary for this class, but using the
// template is the easiest way to create a UDP instance of IOSocket.
@@ -156,24 +176,6 @@ private:
// Placeholder for error code object. It will be passed to ASIO library
// to have it set in case of error.
asio::error_code ec_;
- // The callback functor for internal asynchronous read event. This is
- // stateless (and it will be copied in the ASIO library anyway), so
- // can be const.
- // SunStudio doesn't like a boost::function object to be passed, so
- // we use the wrapper class as a workaround.
- class CallbackWrapper {
- public:
- CallbackWrapper(boost::function<void(const asio::error_code&, size_t)>
- callback) :
- callback_(callback)
- {}
- void operator()(const asio::error_code& error, size_t len) {
- callback_(error, len);
- }
- private:
- boost::function<void(const asio::error_code&, size_t)> callback_;
- };
- const CallbackWrapper recv_callback_;
// Auxiliary functions
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 32a43ce..00536f3 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -77,18 +77,18 @@ TCPServer::TCPServer(io_service& io_service, int fd, int af,
}
namespace {
- // Called by the timeout_ deadline timer if the client takes too long.
- // If not aborted, cancels the given socket
- // (in which case TCPServer::operator() will be called to continue,
- // with an 'aborted' error code
- void do_timeout(asio::ip::tcp::socket& socket,
- const asio::error_code& error)
- {
- if (error != asio::error::operation_aborted) {
- socket.cancel();
- }
+// Called by the timeout_ deadline timer if the client takes too long.
+// If not aborted, cancels the given socket
+// (in which case TCPServer::operator() will be called to continue,
+// with an 'aborted' error code.)
+void doTimeOut(boost::shared_ptr<asio::ip::tcp::socket> socket,
+ const asio::error_code& error)
+{
+ if (error != asio::error::operation_aborted) {
+ socket->cancel();
}
}
+}
void
TCPServer::operator()(asio::error_code ec, size_t length) {
@@ -149,13 +149,16 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
/// asynchronous read call.
data_.reset(new char[MAX_LENGTH]);
- /// Start a timer to drop the connection if it is idle
+ /// Start a timer to drop the connection if it is idle. note that
+ // we pass a shared_ptr of the socket object so that it won't be
+ // destroyed at least until the timeout callback (including abort)
+ // is called.
if (*tcp_recv_timeout_ > 0) {
timeout_.reset(new asio::deadline_timer(io_)); // shouldn't throw
timeout_->expires_from_now( // consider any exception fatal.
boost::posix_time::milliseconds(*tcp_recv_timeout_));
- timeout_->async_wait(boost::bind(&do_timeout, boost::ref(*socket_),
- asio::placeholders::error));
+ timeout_->async_wait(boost::bind(&doTimeOut, socket_,
+ asio::placeholders::error));
}
/// Read the message, in two parts. First, the message length:
@@ -235,8 +238,15 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!done_) {
+ // Explicitly close() isn't necessary for most cases. But for the
+ // very connection, socket_ is shared with the original owner of
+ // the server object and would stay open.
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
+ socket_->close(ec);
+ if (ec) {
+ LOG_DEBUG(logger, 0, ASIODNS_TCP_CLOSE_FAIL).arg(ec.message());
+ }
return;
}
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index dd92dcb..521be1b 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -89,8 +89,8 @@ class ServerStopper {
ServerStopper() : server_to_stop_(NULL) {}
virtual ~ServerStopper(){}
- void setServerToStop(DNSServer* server) {
- server_to_stop_ = server;
+ void setServerToStop(DNSServer& server) {
+ server_to_stop_ = &server;
}
void stopServer() const {
@@ -378,40 +378,36 @@ class DNSServerTestBase : public::testing::Test {
server_port))),
tcp_client_(new TCPClient(service,
ip::tcp::endpoint(server_address_,
- server_port))),
- udp_server_(NULL),
- tcp_server_(NULL)
+ server_port)))
{
current_service = &service;
}
~ DNSServerTestBase() {
- if (udp_server_ != NULL) {
+ if (udp_server_) {
udp_server_->stop();
}
- if (tcp_server_ != NULL) {
+ if (tcp_server_) {
tcp_server_->stop();
}
delete checker_;
delete lookup_;
delete sync_lookup_;
delete answer_;
- delete udp_server_;
delete udp_client_;
- delete tcp_server_;
delete tcp_client_;
// No delete here. The service is not allocated by new, but as our
// member. This only references it, so just cleaning the pointer.
current_service = NULL;
}
- void testStopServerByStopper(DNSServer* server, SimpleClient* client,
- ServerStopper* stopper)
+ void testStopServerByStopper(DNSServer& server, SimpleClient* client,
+ ServerStopper* stopper)
{
static const unsigned int IO_SERVICE_TIME_OUT = 5;
io_service_is_time_out = false;
stopper->setServerToStop(server);
- (*server)();
+ server();
client->sendDataThenWaitForFeedback(query_message);
// Since thread hasn't been introduced into the tool box, using
// signal to make sure run function will eventually return even
@@ -446,8 +442,8 @@ class DNSServerTestBase : public::testing::Test {
SimpleAnswer* const answer_;
UDPClient* const udp_client_;
TCPClient* const tcp_client_;
- UDPServerClass* udp_server_;
- TCPServer* tcp_server_;
+ boost::shared_ptr<UDPServerClass> udp_server_;
+ boost::shared_ptr<TCPServer> tcp_server_;
// To access them in signal handle function, the following
// variables have to be static.
@@ -508,27 +504,30 @@ protected:
this->udp_server_ = createServer(fd_udp, AF_INET6);
const int fd_tcp(getFd(SOCK_STREAM));
ASSERT_NE(-1, fd_tcp) << strerror(errno);
- this->tcp_server_ = new TCPServer(this->service, fd_tcp, AF_INET6,
- this->checker_, this->lookup_,
- this->answer_);
+ this->tcp_server_ =
+ boost::shared_ptr<TCPServer>(new TCPServer(
+ this->service, fd_tcp, AF_INET6,
+ this->checker_, this->lookup_,
+ this->answer_));
}
// A helper factory of the tested UDP server class: allow customization
// by template specialization.
- UDPServerClass* createServer(int fd, int af) {
- return (new UDPServerClass(this->service, fd, af,
- this->checker_, this->lookup_,
- this->answer_));
+ boost::shared_ptr<UDPServerClass> createServer(int fd, int af) {
+ return (boost::shared_ptr<UDPServerClass>(
+ new UDPServerClass(this->service, fd, af,
+ this->checker_, this->lookup_,
+ this->answer_)));
}
};
// Specialization for SyncUDPServer. It needs to use SyncDummyLookup.
template<>
-SyncUDPServer*
+boost::shared_ptr<SyncUDPServer>
FdInit<SyncUDPServer>::createServer(int fd, int af) {
delete this->lookup_;
this->lookup_ = new SyncDummyLookup;
- return (new SyncUDPServer(this->service, fd, af, this->lookup_));
+ return (SyncUDPServer::create(this->service, fd, af, this->lookup_));
}
// This makes it the template as gtest wants it.
@@ -557,7 +556,7 @@ asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
// get response, UDP server will be stopped, the io service won't quit
// if udp server doesn't stop successfully.
TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->udp_client_);
EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -566,18 +565,17 @@ TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
// Test whether udp server stopped successfully before server start to serve
TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
this->udp_server_->stop();
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->udp_client_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
}
-
// Test whether udp server stopped successfully during message check.
// This only works for non-sync server; SyncUDPServer doesn't use checkin
// callback.
TEST_F(AsyncServerTest, stopUDPServerDuringMessageCheck) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->checker_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -585,7 +583,7 @@ TEST_F(AsyncServerTest, stopUDPServerDuringMessageCheck) {
// Test whether udp server stopped successfully during query lookup
TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->lookup_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -595,7 +593,7 @@ TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
// Only works for (non-sync) server because SyncUDPServer doesn't use answer
// callback.
TEST_F(AsyncServerTest, stopUDPServerDuringPrepareAnswer) {
- testStopServerByStopper(udp_server_, udp_client_, answer_);
+ testStopServerByStopper(*udp_server_, udp_client_, answer_);
EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
EXPECT_TRUE(serverStopSucceed());
}
@@ -612,18 +610,17 @@ stopServerManyTimes(DNSServer *server, unsigned int times) {
TYPED_TEST(DNSServerTest, stopUDPServerMoreThanOnce) {
ASSERT_NO_THROW({
boost::function<void()> stop_server_3_times
- = boost::bind(stopServerManyTimes, this->udp_server_, 3);
+ = boost::bind(stopServerManyTimes, this->udp_server_.get(), 3);
this->udp_client_->setGetFeedbackCallback(stop_server_3_times);
- this->testStopServerByStopper(this->udp_server_,
+ this->testStopServerByStopper(*this->udp_server_,
this->udp_client_, this->udp_client_);
EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
});
EXPECT_TRUE(this->serverStopSucceed());
}
-
TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -632,7 +629,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
TYPED_TEST(DNSServerTest, TCPTimeoutOnLen) {
this->tcp_server_->setTCPRecvTimeout(100);
this->tcp_client_->setSendDataLenDelay(2);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("", this->tcp_client_->getReceivedData());
EXPECT_FALSE(this->serverStopSucceed());
@@ -642,7 +639,7 @@ TYPED_TEST(DNSServerTest, TCPTimeout) {
// set delay higher than timeout
this->tcp_server_->setTCPRecvTimeout(100);
this->tcp_client_->setSendDataDelay(2);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("", this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -652,7 +649,7 @@ TYPED_TEST(DNSServerTest, TCPNoTimeout) {
// set delay lower than timeout
this->tcp_server_->setTCPRecvTimeout(3000);
this->tcp_client_->setSendDataDelay(1);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("BIND10 is awesome", this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -661,7 +658,7 @@ TYPED_TEST(DNSServerTest, TCPNoTimeout) {
// Test whether tcp server stopped successfully before server start to serve
TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
this->tcp_server_->stop();
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -670,7 +667,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
// Test whether tcp server stopped successfully during message check
TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->checker_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -678,7 +675,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
// Test whether tcp server stopped successfully during query lookup
TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->lookup_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -686,7 +683,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
// Test whether tcp server stopped successfully during composing answer
TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->answer_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -698,9 +695,9 @@ TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
ASSERT_NO_THROW({
boost::function<void()> stop_server_3_times
- = boost::bind(stopServerManyTimes, this->tcp_server_, 3);
+ = boost::bind(stopServerManyTimes, this->tcp_server_.get(), 3);
this->tcp_client_->setGetFeedbackCallback(stop_server_3_times);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
});
@@ -759,14 +756,36 @@ TEST_F(SyncServerTest, unsupportedOps) {
// Check it rejects forgotten resume (eg. insists that it is synchronous)
TEST_F(SyncServerTest, mustResume) {
lookup_->allow_resume_ = false;
- ASSERT_THROW(testStopServerByStopper(udp_server_, udp_client_, lookup_),
+ ASSERT_THROW(testStopServerByStopper(*udp_server_, udp_client_, lookup_),
isc::Unexpected);
}
// SyncUDPServer doesn't allow NULL lookup callback.
TEST_F(SyncServerTest, nullLookupCallback) {
- EXPECT_THROW(SyncUDPServer(service, 0, AF_INET, NULL),
+ EXPECT_THROW(SyncUDPServer::create(service, 0, AF_INET, NULL),
isc::InvalidParameter);
}
+TEST_F(SyncServerTest, resetUDPServerBeforeEvent) {
+ // Reset the UDP server object after starting and before it would get
+ // an event from io_service (in this case abort event). The following
+ // sequence confirms it's shut down immediately, and without any
+ // disruption.
+
+ // Since we'll stop the server run() should immediately return, and
+ // it's very unlikely to cause hangup. But we'll make very sure it
+ // doesn't happen.
+ const unsigned int IO_SERVICE_TIME_OUT = 5;
+ (*udp_server_)();
+ udp_server_->stop();
+ udp_server_.reset();
+ void (*prev_handler)(int) = std::signal(SIGALRM, stopIOService);
+ current_service = &service;
+ alarm(IO_SERVICE_TIME_OUT);
+ service.run();
+ alarm(0);
+ std::signal(SIGALRM, prev_handler);
+ EXPECT_FALSE(io_service_is_time_out);
+}
+
}
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index 3505982..b2f88f8 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -32,6 +32,7 @@ libb10_asiolink_la_SOURCES += tcp_endpoint.h
libb10_asiolink_la_SOURCES += tcp_socket.h
libb10_asiolink_la_SOURCES += udp_endpoint.h
libb10_asiolink_la_SOURCES += udp_socket.h
+libb10_asiolink_la_SOURCES += local_socket.h local_socket.cc
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
diff --git a/src/lib/asiolink/local_socket.cc b/src/lib/asiolink/local_socket.cc
new file mode 100644
index 0000000..f47226e
--- /dev/null
+++ b/src/lib/asiolink/local_socket.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/local_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_error.h>
+
+#include <asio.hpp>
+
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+class LocalSocket::Impl {
+public:
+ Impl(IOService& io_service, int fd) :
+ asio_sock_(io_service.get_io_service(),
+ asio::local::stream_protocol(), fd)
+ {
+ // Depending on the underlying demultiplex API, the constructor may or
+ // may not throw in case fd is invalid. To catch such cases sooner,
+ // we try to get the local endpoint (we don't need it in the rest of
+ // this implementation).
+ asio_sock_.local_endpoint(ec_);
+ if (ec_) {
+ isc_throw(IOError, "failed to open local socket with FD " << fd
+ << " (local endpoint unknown): " << ec_.message());
+ }
+ }
+
+ asio::local::stream_protocol::socket asio_sock_;
+ asio::error_code ec_;
+};
+
+LocalSocket::LocalSocket(IOService& io_service, int fd) :
+ impl_(NULL)
+{
+ try {
+ impl_ = new Impl(io_service, fd);
+ } catch (const asio::system_error& error) {
+ // Catch and convert any exception from asio's constructor
+ isc_throw(IOError, "failed to open local socket with FD " << fd
+ << ": " << error.what());
+ }
+}
+
+LocalSocket::~LocalSocket() {
+ delete impl_;
+}
+
+int
+LocalSocket::getNative() const {
+ return (impl_->asio_sock_.native());
+}
+
+int
+LocalSocket::getProtocol() const {
+ return (AF_UNIX);
+}
+
+namespace {
+// Wrapper callback for async_read that simply adjusts asio-native parameters
+// for the LocalSocket interface. Note that this is a free function and
+// doesn't rely on internal member variables of LocalSocket.
+// So it can be called safely even after the LocalSocket object on which
+// asyncRead() was called is destroyed.
+void
+readCompleted(const asio::error_code& ec,
+ LocalSocket::ReadCallback user_callback)
+{
+ // assumption check: we pass non empty string iff ec indicates an error.
+ const std::string err_msg = ec ? ec.message() : std::string();
+ assert(ec || err_msg.empty());
+
+ user_callback(err_msg);
+}
+}
+
+void
+LocalSocket::asyncRead(const ReadCallback& callback, void* buf,
+ size_t buflen)
+{
+ asio::async_read(impl_->asio_sock_, asio::buffer(buf, buflen),
+ boost::bind(readCompleted, _1, callback));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/local_socket.h b/src/lib/asiolink/local_socket.h
new file mode 100644
index 0000000..6269b7c
--- /dev/null
+++ b/src/lib/asiolink/local_socket.h
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LOCAL_SOCKET_H
+#define LOCAL_SOCKET_H 1
+
+#include <asiolink/io_socket.h>
+#include <asiolink/io_service.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief A wrapper for ASIO stream socket in the local (AF_UNIX) domain.
+///
+/// This class provides a simple, limited set of wrapper interfaces to an
+/// ASIO stream socket object in the local domain. Unlike other concrete
+/// derived classes of \c IOSocket, this class is intended to be instantiated
+/// directly. Right now it only provides read interface due to the limited
+/// expected usage, but it can be extended as we see need for other operations
+/// on this socket.
+///
+/// Note that in the initial implementation there's even no stop() or cancel()
+/// method; for these cases users are expected to just destroy the socket
+/// object (this may be extended in future, too).
+class LocalSocket : boost::noncopyable, public IOSocket {
+public:
+ /// \brief Constructor from a native file descriptor of AF_UNIX stream
+ /// socket.
+ ///
+ /// Parameter \c fd must be an open stream-type socket of the AF_UNIX
+ /// domain. The constructor tries to detect some invalid cases, but
+ /// it may not reject all invalid cases. It's generally the
+ /// responsibility of the caller.
+ ///
+ /// \throw IOError Failed to create the socket object, most likely because
+ /// the given file descriptor is invalid.
+ ///
+ /// \param io_service The IO service object to handle events on this
+ /// socket.
+ /// \param fd File descriptor of an AF_UNIX socket.
+ LocalSocket(IOService& io_service, int fd);
+
+ /// \brief Destructor.
+ ///
+ /// \throw None.
+ virtual ~LocalSocket();
+
+ /// \brief Local socket version of getNative().
+ ///
+ /// \throw None.
+ virtual int getNative() const;
+
+ /// \brief Local socket version of getProtocol().
+ ///
+ /// It always returns \c AF_UNIX.
+ ///
+ /// \throw None.
+ virtual int getProtocol() const;
+
+ /// \brief The callback functor for the \c asyncRead method.
+ ///
+ /// The callback takes one parameter, \c error. It will be set to
+ /// non empty string if read operation fails and the string explains
+ /// the reason for the failure. On success \c error will be empty.
+ typedef boost::function<void(const std::string& error)> ReadCallback;
+
+ /// \brief Start asynchronous read on the socket.
+ ///
+ /// This method registers an interest on a new read event on the local
+ /// socket for the specified length of data (\c buflen bytes). This
+ /// method returns immediately. When the specified amount of data
+ /// are available for read from the socket or an error happens, the
+ /// specified callback will be called. In the former case the data are
+ /// copied into the given buffer (pointed to by \c buf); in the latter
+ /// case, the \c error parameter of the callback will be set to a non
+ /// empty string.
+ ///
+ /// In the case of error, this socket should be considered
+ /// unusable anymore, because this class doesn't provide a feasible way
+ /// to identify where in the input stream to restart reading. So,
+ /// in practice, the user of this socket should destroy this socket,
+ /// and, if necessary to continue, create a new one.
+ ///
+ /// \c buf must point to a memory region that has at least \c buflen
+ /// bytes of valid space. That region must be kept valid until the
+ /// callback is called or the \c IOService passed to the constructor
+ /// is stopped. This method and class do not check these conditions;
+ /// it's the caller's responsibility to guarantee them.
+ ///
+ /// \note If asyncRead() has been called and hasn't been completed (with
+ /// the callback being called), it's possible that the callback is called
+ /// even after the \c LocalSocket object is destroyed. So the caller
+ /// has to make sure that either \c LocalSocket is valid until the
+ /// callback is called or the callback does not depend on \c LocalSocket;
+ /// alternatively, the caller can stop the \c IOService. This will make
+ /// sure the callback will not be called regardless of when and how
+ /// the \c LocalSocket is destroyed.
+ ///
+ /// \throw None.
+ ///
+ /// \brief callback The callback functor to be called on the completion
+ /// of read.
+ /// \brief buf Buffer to read in data from the socket.
+ /// \brief buflen Length of data to read.
+ void asyncRead(const ReadCallback& callback, void* buf, size_t buflen);
+
+private:
+ class Impl;
+ Impl* impl_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // LOCAL_SOCKET_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index 70b94dc..530fe0b 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -34,6 +34,7 @@ run_unittests_SOURCES += tcp_socket_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += local_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/asiolink/tests/local_socket_unittest.cc b/src/lib/asiolink/tests/local_socket_unittest.cc
new file mode 100644
index 0000000..72efd6e
--- /dev/null
+++ b/src/lib/asiolink/tests/local_socket_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/local_socket.h>
+#include <asiolink/io_error.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
+#include <csignal>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+#include <unistd.h> // for alarm(3)
+
+using namespace isc::asiolink;
+
+namespace {
+
+// duration (in seconds) until we break possible hangup; value is an
+// arbitrary choice.
+const unsigned IO_TIMEOUT = 10;
+
+// A simple RAII wrapper for a file descriptor so test sockets are safely
+// closed in each test.
+class ScopedSocket : boost::noncopyable {
+public:
+ ScopedSocket() : fd_(-1) {}
+ ~ScopedSocket() {
+ if (fd_ >= 0) {
+ EXPECT_EQ(0, ::close(fd_));
+ }
+ }
+ void set(int fd) {
+ assert(fd_ == -1);
+ fd_ = fd;
+ }
+ int get() { return (fd_); }
+ int release() {
+ const int ret = fd_;
+ fd_ = -1;
+ return (ret);
+ }
+private:
+ int fd_;
+};
+
+class LocalSocketTest : public ::testing::Test {
+protected:
+ LocalSocketTest() {
+ int sock_pair[2];
+ EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair));
+ sock_pair_[0].set(sock_pair[0]);
+ sock_pair_[1].set(sock_pair[1]);
+
+ // For tests using actual I/O we use a timer to prevent hangup
+ // due to a bug. Set up the signal handler for the timer here.
+ g_io_service_ = &io_service_;
+ prev_handler_ = std::signal(SIGALRM, stopIOService);
+ }
+
+ ~LocalSocketTest() {
+ alarm(0);
+ // reset the global to NULL to detect any invalid access to freed
+ // io_service (this shouldn't happen, so we don't change stopIOService
+ // itself)
+ g_io_service_ = NULL;
+ std::signal(SIGALRM, prev_handler_);
+ }
+
+ // Common set of tests for async read
+ void checkAsyncRead(size_t data_len);
+
+ IOService io_service_;
+ ScopedSocket sock_pair_[2];
+ std::vector<uint8_t> read_buf_;
+private:
+ static IOService* g_io_service_; // will be set to &io_service_
+ void (*prev_handler_)(int);
+
+ // SIGALRM handler to prevent hangup. This must be a static method
+ // so it can be passed to std::signal().
+ static void stopIOService(int) {
+ g_io_service_->stop();
+ }
+};
+
+IOService* LocalSocketTest::g_io_service_ = NULL;
+
+TEST_F(LocalSocketTest, construct) {
+ const int fd = sock_pair_[0].release();
+ LocalSocket sock(io_service_, fd);
+ EXPECT_EQ(fd, sock.getNative());
+ EXPECT_EQ(AF_UNIX, sock.getProtocol());
+}
+
+TEST_F(LocalSocketTest, constructError) {
+ // try to construct a LocalSocket object with a closed socket. It should
+ // fail.
+ const int fd = sock_pair_[0].release();
+ EXPECT_EQ(0, close(fd));
+ EXPECT_THROW(LocalSocket(io_service_, fd), IOError);
+}
+
+TEST_F(LocalSocketTest, autoClose) {
+ // Confirm that passed FD will be closed on destruction of LocalSocket
+ const int fd = sock_pair_[0].release();
+ {
+ LocalSocket sock(io_service_, fd);
+ }
+ // fd should have been closed, so close() should fail (we assume there's
+ // no other open() call since then)
+ EXPECT_EQ(-1, ::close(fd));
+}
+
+void
+callback(const std::string& error, IOService* io_service, bool* called,
+ bool expect_error)
+{
+ if (expect_error) {
+ EXPECT_NE("", error);
+ } else {
+ EXPECT_EQ("", error);
+ }
+ *called = true;
+ io_service->stop();
+}
+
+void
+LocalSocketTest::checkAsyncRead(size_t data_len) {
+ LocalSocket sock(io_service_, sock_pair_[0].release());
+ bool callback_called = false;
+ read_buf_.resize(data_len);
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ false), &read_buf_[0], data_len);
+
+ std::vector<uint8_t> expected_data(data_len);
+ for (size_t i = 0; i < data_len; ++i) {
+ expected_data[i] = i & 0xff;
+ }
+ alarm(IO_TIMEOUT);
+ // If write blocks, it will eventually fail due to signal interruption.
+ // Since io_service has been stopped already, run() would immediately
+ // return and test should complete (with failure). But to make very sure
+ // it never cause hangup we rather return from the test at the point of
+ // failure of write. In either case it signals a failure and need for
+ // a fix.
+ ASSERT_EQ(data_len, write(sock_pair_[1].get(), &expected_data[0],
+ data_len));
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+ EXPECT_EQ(0, std::memcmp(&expected_data[0], &read_buf_[0], data_len));
+
+}
+
+TEST_F(LocalSocketTest, asyncRead) {
+ // A simple case of asynchronous read: wait for 1 byte and successfully
+ // read it in the run() loop.
+ checkAsyncRead(1);
+}
+
+TEST_F(LocalSocketTest, asyncLargeRead) {
+ // Similar to the previous case, but for moderately larger data.
+ // (for the moment) we don't expect to use this interface with much
+ // larger data that could cause blocking write.
+ checkAsyncRead(1024);
+}
+
+TEST_F(LocalSocketTest, asyncPartialRead) {
+ alarm(IO_TIMEOUT);
+
+ // specify reading 4 bytes of data, and send 3 bytes. It shouldn't cause
+ // callback. while we actually don't use the buffer, we'll initialize it
+ // to make valgrind happy.
+ char recv_buf[4];
+ std::memset(recv_buf, 0, sizeof(recv_buf));
+ bool callback_called = false;
+ LocalSocket sock(io_service_, sock_pair_[0].release());
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ false), recv_buf, sizeof(recv_buf));
+ EXPECT_EQ(3, write(sock_pair_[1].get(), recv_buf, 3));
+
+ // open another pair of sockets so we can stop the IO service after run.
+ int socks[2];
+ char ch = 0;
+ EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
+ ScopedSocket aux_sockpair[2];
+ aux_sockpair[0].set(socks[0]);
+ aux_sockpair[1].set(socks[1]);
+ LocalSocket aux_sock(io_service_, aux_sockpair[0].get());
+ aux_sockpair[0].release(); // on successful construction we should release
+ bool aux_callback_called = false;
+ aux_sock.asyncRead(boost::bind(&callback, _1, &io_service_,
+ &aux_callback_called, false), &ch, 1);
+ EXPECT_EQ(1, write(aux_sockpair[1].get(), &ch, 1));
+
+ // run the IO service, it will soon be stopped via the auxiliary callback.
+ // the main callback shouldn't be called.
+ io_service_.run();
+ EXPECT_FALSE(callback_called);
+ EXPECT_TRUE(aux_callback_called);
+}
+
+TEST_F(LocalSocketTest, asyncReadError) {
+ const int sock_fd = sock_pair_[0].release();
+ LocalSocket sock(io_service_, sock_fd);
+ bool callback_called = false;
+ read_buf_.resize(1);
+ read_buf_.at(0) = 53; // dummy data to check it later
+ const char ch = 35; // send different data to the read socket with data
+ EXPECT_EQ(1, write(sock_pair_[1].get(), &ch, 1));
+ close(sock_fd); // invalidate the read socket
+ // we'll get callback with an error (e.g. 'bad file descriptor)
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ true), &read_buf_[0], 1);
+
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+ EXPECT_EQ(53, read_buf_.at(0));
+}
+
+TEST_F(LocalSocketTest, asyncReadThenDestroy) {
+ // destroy the socket before running the IO service. we'll still get
+ // callback with an error.
+ boost::scoped_ptr<LocalSocket> sock(
+ new LocalSocket(io_service_, sock_pair_[0].release()));
+ read_buf_.resize(1);
+ bool callback_called = false;
+ sock->asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ true), &read_buf_[0], 1);
+ sock.reset();
+
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+}
+
+}
diff --git a/src/lib/cc/proto_defs.cc b/src/lib/cc/proto_defs.cc
index 8f7fb91..2806c2a 100644
--- a/src/lib/cc/proto_defs.cc
+++ b/src/lib/cc/proto_defs.cc
@@ -43,6 +43,8 @@ const char* const CC_COMMAND_STOP = "stop";
// The wildcards of some headers
const char* const CC_TO_WILDCARD = "*";
const char* const CC_INSTANCE_WILDCARD = "*";
+// Prefixes for groups
+const char* const CC_GROUP_NOTIFICATION_PREFIX = "notifications/";
// Reply codes
const int CC_REPLY_NO_RECPT = -1;
const int CC_REPLY_SUCCESS = 0;
@@ -50,6 +52,7 @@ const int CC_REPLY_SUCCESS = 0;
const char *const CC_PAYLOAD_LNAME = "lname";
const char *const CC_PAYLOAD_RESULT = "result";
const char *const CC_PAYLOAD_COMMAND = "command";
+const char *const CC_PAYLOAD_NOTIFICATION = "notification";
}
}
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index d094ab9..0d43b27 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -884,5 +884,21 @@ ModuleCCSession::rpcCall(const std::string &command, const std::string &group,
}
}
+void
+ModuleCCSession::notify(const std::string& group, const std::string& name,
+ const ConstElementPtr& params)
+{
+ const ElementPtr message(Element::createMap());
+ const ElementPtr notification(Element::createList());
+ notification->add(Element::create(name));
+ if (params) {
+ notification->add(params);
+ }
+ message->set(isc::cc::CC_PAYLOAD_NOTIFICATION, notification);
+ groupSendMsg(message, isc::cc::CC_GROUP_NOTIFICATION_PREFIX + group,
+ isc::cc::CC_INSTANCE_WILDCARD,
+ isc::cc::CC_TO_WILDCARD, false);
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 995a5cd..04e3046 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -425,6 +425,31 @@ public:
params =
isc::data::ConstElementPtr());
+ /// \brief Send a notification to subscribed users
+ ///
+ /// Send a notification message to all users subscribed to the given
+ /// notification group.
+ ///
+ /// This method does not not block.
+ ///
+ /// See docs/design/ipc-high.txt for details about notifications and
+ /// the format of messages sent.
+ ///
+ /// \throw CCSessionError for low-level communication errors.
+ /// \param notification_group This parameter (indirectly) signifies what
+ /// users should receive the notification. Only the users that
+ /// subscribed to notifications on the same group receive it.
+ /// \param name The name of the event to notify about (for example
+ /// `new_group_member`).
+ /// \param params Other parameters that describe the event. This might
+ /// be, for example, the ID of the new member and the name of the
+ /// group. This can be any data element, but it is common for it to be
+ /// map.
+ void notify(const std::string& notification_group,
+ const std::string& name,
+ const isc::data::ConstElementPtr& params =
+ isc::data::ConstElementPtr());
+
/// \brief Convenience version of rpcCall
///
/// This is exactly the same as the previous version of rpcCall, except
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index c11cd24..700ffe4 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -117,6 +117,46 @@ TEST_F(CCSessionTest, rpcNoRecpt) {
RPCRecipientMissing);
}
+// Test sending a notification
+TEST_F(CCSessionTest, notify) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+ false);
+ mccs.notify("group", "event", el("{\"param\": true}"));
+ const ConstElementPtr notification(el(
+ "["
+ " \"notifications/group\","
+ " \"*\","
+ " {"
+ " \"notification\": ["
+ " \"event\", {"
+ " \"param\": true"
+ " }"
+ " ]"
+ " },"
+ " -1"
+ "]"));
+ EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+}
+
+// Test sending a notification
+TEST_F(CCSessionTest, notifyNoParams) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+ false);
+ mccs.notify("group", "event");
+ const ConstElementPtr notification(el(
+ "["
+ " \"notifications/group\","
+ " \"*\","
+ " {"
+ " \"notification\": [\"event\"]"
+ " },"
+ " -1"
+ "]"));
+ EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+}
+
TEST_F(CCSessionTest, createAnswer) {
ConstElementPtr answer;
answer = createAnswer();
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 169e461..4145700 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -132,8 +132,8 @@ public:
/// \brief A helper structure to represent the search result of
/// \c find().
///
- /// This is a straightforward pair of the result code and a share pointer
- /// to the found zone to represent the result of \c find().
+ /// This is a straightforward tuple of the result code/flags and a shared
+ /// pointer to the found zone to represent the result of \c find().
/// We use this in order to avoid overloading the return value for both
/// the result code ("success" or "not found") and the found object,
/// i.e., avoid using \c NULL to mean "not found", etc.
@@ -146,10 +146,13 @@ public:
/// variables.
struct FindResult {
FindResult(result::Result param_code,
- const ZoneFinderPtr param_zone_finder) :
- code(param_code), zone_finder(param_zone_finder)
+ const ZoneFinderPtr param_zone_finder,
+ result::ResultFlags param_flags = result::FLAGS_DEFAULT) :
+ code(param_code), flags(param_flags),
+ zone_finder(param_zone_finder)
{}
const result::Result code;
+ const result::ResultFlags flags;
const ZoneFinderPtr zone_finder;
};
@@ -184,8 +187,12 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
/// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
+ /// - \c flags: usually FLAGS_DEFAULT, but if the zone data are not
+ /// available (possibly because an error was detected at load time)
+ /// the ZONE_EMPTY flag is set.
/// - \c zone_finder: Pointer to a \c ZoneFinder object for the found zone
- /// if one is found; otherwise \c NULL.
+ /// if one is found and is not empty (flags doesn't have ZONE_EMPTY);
+ /// otherwise \c NULL.
///
/// A specific derived version of this method may throw an exception.
/// This interface does not specify which exceptions can happen (at least
@@ -215,6 +222,9 @@ public:
/// \throw Others Possibly implementation specific exceptions (it is
/// not fixed if a concrete implementation of this method can throw
/// anything else.)
+ /// \throw EmptyZone the zone is supposed to exist in the data source,
+ /// but its content is not available. This generally means there's an
+ /// error in the content.
///
/// \param name The name of zone apex to be traversed. It doesn't do
/// nearest match as findZone.
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 0611ed9..f09b504 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -90,15 +90,15 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
for (; i < config->size(); ++i) {
// Extract the parameters
const ConstElementPtr dconf(config->get(i));
- const ConstElementPtr typeElem(dconf->get("type"));
- if (typeElem == ConstElementPtr()) {
+ const ConstElementPtr type_elem(dconf->get("type"));
+ if (type_elem == ConstElementPtr()) {
isc_throw(ConfigurationError, "Missing the type option in "
"data source no " << i);
}
- const string type(typeElem->stringValue());
- ConstElementPtr paramConf(dconf->get("params"));
- if (paramConf == ConstElementPtr()) {
- paramConf.reset(new NullElement());
+ const string type(type_elem->stringValue());
+ ConstElementPtr param_conf(dconf->get("params"));
+ if (param_conf == ConstElementPtr()) {
+ param_conf.reset(new NullElement());
}
// Get the name (either explicit, or guess)
const ConstElementPtr name_elem(dconf->get("name"));
@@ -114,7 +114,7 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
// no-op. In the latter case, it's of no use unless cache is
// allowed; we simply skip building it in that case.
const DataSourcePair dsrc_pair = getDataSourceClient(type,
- paramConf);
+ param_conf);
if (!allow_cache && !dsrc_pair.first) {
LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
arg(datasrc_name).arg(rrclass_);
@@ -159,18 +159,22 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
cache_conf->getLoadAction(rrclass_, zname);
// in this loop this should be always true
assert(load_action);
- memory::ZoneWriter writer(zt_segment,
- load_action, zname, rrclass_);
- writer.load();
+ // For the initial load, we'll let the writer handle
+ // loading error and install an empty zone in the table.
+ memory::ZoneWriter writer(zt_segment, load_action, zname,
+ rrclass_, true);
+
+ std::string error_msg;
+ writer.load(&error_msg);
+ if (!error_msg.empty()) {
+ LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR).arg(zname).
+ arg(rrclass_).arg(datasrc_name).arg(error_msg);
+ }
writer.install();
writer.cleanup();
} catch (const NoSuchZone&) {
LOG_ERROR(logger, DATASRC_CACHE_ZONE_NOTFOUND).
arg(zname).arg(rrclass_).arg(datasrc_name);
- } catch (const ZoneLoaderException& e) {
- LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR).
- arg(zname).arg(rrclass_).arg(datasrc_name).
- arg(e.what());
}
}
}
@@ -357,7 +361,7 @@ ConfigurableClientList::getCachedZoneWriter(const Name& name,
ZoneWriterPtr(
new memory::ZoneWriter(
*info.ztable_segment_,
- load_action, name, rrclass_))));
+ load_action, name, rrclass_, false))));
}
// We can't find the specified zone. If a specific data source was
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index 73bc2b3..eb1c1ec 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -231,20 +231,29 @@ public:
/// of the searched name is needed. Therefore, the call would look like:
///
/// \code FindResult result(list->find(queried_name));
- /// FindResult result(list->find(queried_name));
- /// if (result.datasrc_) {
- /// createTheAnswer(result.finder_);
+ /// if (result.dsrc_client_) {
+ /// if (result.finder_) {
+ /// createTheAnswer(result.finder_);
+ /// } else { // broken zone, return SERVFAIL
+ /// createErrorMessage(Rcode.SERVFAIL());
+ /// }
/// } else {
/// createNotAuthAnswer();
/// } \endcode
///
+ /// Note that it is possible that \c finder_ is NULL while \c datasrc_
+ /// is not. This happens if the zone is configured to be served from
+ /// the data source but it is broken in some sense and doesn't hold any
+ /// zone data, e.g., when the zone file has an error or the secondary
+ /// zone hasn't been transferred yet. The caller needs to expect the case
+ /// and handle it accordingly.
+ ///
/// The other scenario is manipulating zone data (XfrOut, XfrIn, DDNS,
/// ...). In this case, the finder itself is not so important. However,
/// we need an exact match (if we want to manipulate zone data, we must
/// know exactly, which zone we are about to manipulate). Then the call
///
/// \code FindResult result(list->find(zone_name, true, false));
- /// FindResult result(list->find(zone_name, true, false));
/// if (result.datasrc_) {
/// ZoneUpdaterPtr updater(result.datasrc_->getUpdater(zone_name);
/// ...
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index dbba18d..6667349 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -374,8 +374,9 @@ database backend (sqlite3, for example) and use it from there.
During data source configuration, an error was found in the zone data
when it was being loaded in to memory on the shown data source. This
particular zone was not loaded, but data source configuration
-continues, possibly loading other zones into memory. The specific
-error is shown in the message, and should be addressed.
+continues, possibly loading other zones into memory. Subsequent lookups
+will treat this zone as broken. The specific error is shown in the
+message, and should be addressed.
% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
There's an error in the given master file. The zone won't be loaded for
diff --git a/src/lib/datasrc/exceptions.h b/src/lib/datasrc/exceptions.h
index d3b25da..f3bc06f 100644
--- a/src/lib/datasrc/exceptions.h
+++ b/src/lib/datasrc/exceptions.h
@@ -56,6 +56,17 @@ public:
DataSourceError(file, line, what) {}
};
+/// \brief An error indicating a zone is recognized but its content is not
+/// available.
+///
+/// This generally indicates a condition that there's an error in the zone
+/// content and it's not successfully loaded.
+class EmptyZone : public DataSourceError {
+public:
+ EmptyZone(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
/// Base class for a number of exceptions that are thrown while working
/// with zones.
struct ZoneException : public Exception {
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 44fe806..434eaf2 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -17,6 +17,7 @@ libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
libdatasrc_memory_la_SOURCES += rrset_collection.h rrset_collection.cc
libdatasrc_memory_la_SOURCES += segment_object_holder.h
+libdatasrc_memory_la_SOURCES += segment_object_holder.cc
libdatasrc_memory_la_SOURCES += logger.h logger.cc
libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index d503a11..4c6199a 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -1301,15 +1301,24 @@ public:
/// doesn't exist.
///
/// This method normally involves resource allocation. If it fails
- /// the corresponding standard exception will be thrown.
+ /// \c std::bad_alloc will be thrown. Also, depending on details of the
+ /// specific \c MemorySegment, it can propagate the \c MemorySegmentGrown
+ /// exception.
///
/// This method does not provide the strong exception guarantee in its
- /// strict sense; if an exception is thrown in the middle of this
- /// method, the internal structure may change. However, it should
- /// still retain the same property as a mapping container before this
- /// method is called. For example, the result of \c find() should be
- /// the same. This method provides the weak exception guarantee in its
- /// normal sense.
+ /// strict sense; there can be new empty nodes that are superdomains of
+ /// the domain to be inserted as a side effect. However, the tree
+ /// retains internal integrity otherwise, and, in particular, the intended
+ /// insert operation is "resumable": if the \c insert() method is called
+ /// again with the same argument after resolving the cause of the
+ /// exception (possibly multiple times), it should now succeed. Note,
+ /// however, that in case of \c MemorySegmentGrown the address of the
+ /// `DomainTree` object may have been reallocated if it was created with
+ /// the same \c MemorySegment (which will often be the case in practice).
+ /// So the caller may have to re-get the address before calling \c insert
+ /// again. It can be done using the concept of "named addresses" of
+ /// \c MemorySegment, or the direct caller may not have to worry about it
+ /// if this condition is guaranteed at a higher level.
///
/// \param mem_sgmt A \c MemorySegment object for allocating memory of
/// a new node to be inserted. Must be the same segment as that used
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index a6342de..0b0c827 100644
--- a/src/lib/datasrc/memory/memory_client.cc
+++ b/src/lib/datasrc/memory/memory_client.cc
@@ -69,11 +69,11 @@ InMemoryClient::findZone(const isc::dns::Name& zone_name) const {
const ZoneTable::FindResult result(zone_table->findZone(zone_name));
ZoneFinderPtr finder;
- if (result.code != result::NOTFOUND) {
+ if (result.code != result::NOTFOUND && result.zone_data) {
finder.reset(new InMemoryZoneFinder(*result.zone_data, getClass()));
}
- return (DataSourceClient::FindResult(result.code, finder));
+ return (DataSourceClient::FindResult(result.code, finder, result.flags));
}
const ZoneData*
@@ -242,7 +242,12 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
const ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
const ZoneTable::FindResult result(zone_table->findZone(name));
if (result.code != result::SUCCESS) {
- isc_throw(NoSuchZone, "No such zone: " + name.toText());
+ isc_throw(NoSuchZone, "no such zone for in-memory iterator: "
+ << name.toText());
+ }
+ if (!result.zone_data) {
+ isc_throw(EmptyZone, "empty zone for in-memory iterator: "
+ << name.toText());
}
return (ZoneIteratorPtr(new MemoryIterator(
diff --git a/src/lib/datasrc/memory/memory_messages.mes b/src/lib/datasrc/memory/memory_messages.mes
index cf51706..37539e7 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -92,6 +92,12 @@ tried).
Debug information. A specific type RRset is requested at a zone origin
of an in-memory zone and it is found.
+% DATASRC_MEMORY_MEM_ADD_EMPTY_ZONE adding an empty zone '%1/%2'
+Debug information. An "empty" zone is being added into the in-memory
+data source. This is conceptual data indicating the state where the
+zone exists but its content isn't available. That would be the case,
+for example, a broken zone specified in the configuration.
+
% DATASRC_MEMORY_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
Debug information. An RRset is being added to the in-memory data source.
diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h
index 58201b1..caef551 100644
--- a/src/lib/datasrc/memory/rdataset.h
+++ b/src/lib/datasrc/memory/rdataset.h
@@ -176,6 +176,15 @@ public:
/// it cannot contain more than 65535 RRSIGs. If the given RRset(s) fail
/// to meet this condition, an \c RdataSetError exception will be thrown.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw isc::BadValue Given RRset(s) are invalid (see the description)
/// \throw RdataSetError Number of RDATAs exceed the limits
/// \throw std::bad_alloc Memory allocation fails.
diff --git a/src/lib/datasrc/memory/segment_object_holder.cc b/src/lib/datasrc/memory/segment_object_holder.cc
new file mode 100644
index 0000000..6d47b9d
--- /dev/null
+++ b/src/lib/datasrc/memory/segment_object_holder.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "segment_object_holder.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <stdint.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+std::string
+getNextHolderName() {
+ static uint64_t index = 0;
+ ++index;
+ // in practice we should be able to assume this, uint64 is large
+ // and should not overflow
+ assert(index != 0);
+ return ("Segment object holder auto name " +
+ boost::lexical_cast<std::string>(index));
+}
+
+}
+}
+}
+}
diff --git a/src/lib/datasrc/memory/segment_object_holder.h b/src/lib/datasrc/memory/segment_object_holder.h
index 384f4ef..a716d4a 100644
--- a/src/lib/datasrc/memory/segment_object_holder.h
+++ b/src/lib/datasrc/memory/segment_object_holder.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,39 +16,99 @@
#define DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H 1
#include <util/memory_segment.h>
+#include <string>
+#include <cassert>
namespace isc {
namespace datasrc {
namespace memory {
namespace detail {
+// Internal function to get next yet unused name of segment holder.
+// We need the names of holders to be unique per segment at any given
+// momemnt. This just keeps incrementing number after a prefix with
+// each call, it should be enough (we assert it does not wrap around,
+// but 64bits should be enough).
+//
+// Also, it is not thread safe.
+std::string
+getNextHolderName();
+
// A simple holder to create and use some objects in this implementation
// in an exception safe manner. It works like std::auto_ptr but much
// more simplified.
+//
+// Note, however, that it doesn't take the pointer to hold on construction.
+// This is because the constructor itself can throw or cause address
+// reallocation inside the memory segment. If that happens various
+// undesirable effects can happen, such as memory leak or unintentional access
+// to the pre-reallocated address. To make it safer, we use a separate
+// \c set() method, which is exception free and doesn't cause address
+// reallocation. So the typical usage is to first construct the holder
+// object, then the object to be held, immediately followed by a call to \c
+// set(). Subsequent access to the held address should be done via the \c get()
+// method. get() ensures the address is always valid in the memory segment
+// even if address reallocation happens between set() and get().
+//
// template parameter T is the type of object allocated by mem_sgmt.
// template parameter ARG_T is the type that will be passed to destroy()
// (deleter functor, etc). It must be copyable.
template <typename T, typename ARG_T>
class SegmentObjectHolder {
public:
- SegmentObjectHolder(util::MemorySegment& mem_sgmt, T* obj, ARG_T arg) :
- mem_sgmt_(mem_sgmt), obj_(obj), arg_(arg)
- {}
+ SegmentObjectHolder(util::MemorySegment& mem_sgmt, ARG_T arg) :
+ mem_sgmt_(mem_sgmt), arg_(arg),
+ holder_name_(getNextHolderName()), holding_(true)
+ {
+ if (mem_sgmt_.setNamedAddress(holder_name_.c_str(), NULL)) {
+ // OK. We've grown. The caller might need to be informed, so
+ // we throw. But then, we don't get our destructor, so we
+ // release the memory right away.
+ mem_sgmt_.clearNamedAddress(holder_name_.c_str());
+ isc_throw(isc::util::MemorySegmentGrown,
+ "Segment grown when allocating holder");
+ }
+ }
~SegmentObjectHolder() {
- if (obj_ != NULL) {
- T::destroy(mem_sgmt_, obj_, arg_);
+ if (holding_) {
+ // Use release, as it removes the stored address from segment
+ T* obj = release();
+ if (obj) { // May be NULL if set wasn't called
+ T::destroy(mem_sgmt_, obj, arg_);
+ }
+ }
+ }
+ void set(T* obj) {
+ const bool grown = mem_sgmt_.setNamedAddress(holder_name_.c_str(),
+ obj);
+ // We reserve the space in the constructor, should not grow now
+ assert(!grown);
+ }
+ T* get() {
+ if (holding_) {
+ const util::MemorySegment::NamedAddressResult result =
+ mem_sgmt_.getNamedAddress(holder_name_.c_str());
+ assert(result.first);
+ return (static_cast<T*>(result.second));
+ } else {
+ return (NULL);
}
}
- T* get() { return (obj_); }
T* release() {
- T* ret = obj_;
- obj_ = NULL;
- return (ret);
+ if (holding_) {
+ T* obj = get();
+ mem_sgmt_.clearNamedAddress(holder_name_.c_str());
+ holding_ = false;
+ return (obj);
+ } else {
+ return (NULL);
+ }
}
private:
util::MemorySegment& mem_sgmt_;
- T* obj_;
ARG_T arg_;
+ const std::string holder_name_;
+ bool holding_;
};
} // detail
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index d32fc87..cdcb683 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -38,6 +38,10 @@ namespace isc {
namespace datasrc {
namespace memory {
+// Definition of a class static constant. It's public and its address
+// could be needed by applications, so we need an explicit definition.
+const ZoneNode::Flags ZoneData::DNSSEC_SIGNED;
+
namespace {
void
rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
@@ -91,8 +95,8 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt,
// (with an assertion check for that).
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
const ZoneTree::Result result =
@@ -165,8 +169,8 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
// NSEC3Data::create().
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
ZoneNode* origin_node = NULL;
@@ -179,6 +183,13 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
return (zone_data);
}
+ZoneData*
+ZoneData::create(util::MemorySegment& mem_sgmt) {
+ ZoneData* zone_data = create(mem_sgmt, Name::ROOT_NAME());
+ zone_data->origin_node_->setFlag(EMPTY_ZONE);
+ return (zone_data);
+}
+
void
ZoneData::destroy(util::MemorySegment& mem_sgmt, ZoneData* zone_data,
RRClass zone_class)
diff --git a/src/lib/datasrc/memory/zone_data.h b/src/lib/datasrc/memory/zone_data.h
index c6b3dcc..b4c65f7 100644
--- a/src/lib/datasrc/memory/zone_data.h
+++ b/src/lib/datasrc/memory/zone_data.h
@@ -86,6 +86,15 @@ public:
/// The NSEC3 parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -102,6 +111,15 @@ public:
/// The NSEC3 hash parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -360,21 +378,40 @@ private:
/// It never throws an exception.
ZoneData(ZoneTree* zone_tree, ZoneNode* origin_node);
- // Zone node flags.
+ // Zone node flags. When adding a new flag, it's generally advisable to
+ // keep existing values so the binary image of the data is as much
+ // backward compatible as possible. And it can be helpful in practice
+ // for file-mapped data.
private:
// Set in the origin node (which always exists at the same address)
// to indicate whether the zone is signed or not. Internal use,
// so defined as private.
static const ZoneNode::Flags DNSSEC_SIGNED = ZoneNode::FLAG_USER1;
+
public:
/// \brief Node flag indicating it is at a "wildcard level"
///
/// This means one of the node's immediate children is a wildcard.
static const ZoneNode::Flags WILDCARD_NODE = ZoneNode::FLAG_USER2;
+private:
+ // Also set in the origin node, indicating this is a special "empty zone",
+ // that could be created only by the corresponding create() method to be
+ // used for some kind of sentinel data.
+ static const ZoneNode::Flags EMPTY_ZONE = ZoneNode::FLAG_USER3;
+
public:
/// \brief Allocate and construct \c ZoneData.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -383,6 +420,23 @@ public:
static ZoneData* create(util::MemorySegment& mem_sgmt,
const dns::Name& zone_origin);
+ /// \brief Allocate and construct a special "empty" \c ZoneData.
+ ///
+ /// A ZoneData object created this way holds all internal integrity
+ /// that those created by the other \c create() method have, but is not
+ /// publicly associated with any actual zone data. It's intended to be
+ /// used as a kind of sentinel data to representing the concept such as
+ /// "broken zone".
+ ///
+ /// Methods calls on empty \c ZoneData object except \c destroy() and
+ /// \c isEmpty() are meaningless, while they shouldn't cause disruption.
+ /// It's caller's responsibility to use empty zone data objects in the
+ /// intended way.
+ ///
+ /// \param mem_sgmt A \c MemorySegment from which memory for the new
+ /// \c ZoneData is allocated.
+ static ZoneData* create(util::MemorySegment& mem_sgmt);
+
/// \brief Destruct and deallocate \c ZoneData.
///
/// It releases all resource allocated in the internal storage NSEC3 for
@@ -455,6 +509,13 @@ public:
/// \throw none
bool isNSEC3Signed() const { return (nsec3_data_); }
+ /// \brief Return whether or not the zone data is "empty".
+ ///
+ /// See the description of \c create() for the concept of empty zone data.
+ ///
+ /// \throw None
+ bool isEmpty() const { return (origin_node_->getFlag(EMPTY_ZONE)); }
+
/// \brief Return NSEC3Data of the zone.
///
/// This method returns non-NULL valid pointer to \c NSEC3Data object
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index d66fb3b..e796dd4 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -131,7 +131,7 @@ ZoneDataLoader::flushNodeRRsets() {
}
// Normally rrsigsets map should be empty at this point, but it's still
- // possible that an RRSIG that don't has covered RRset is added; they
+ // possible that an RRSIG that doesn't have covered RRset is added; they
// still remain in the map. We add them to the zone separately.
BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
updater_.add(ConstRRsetPtr(), val.second);
@@ -182,35 +182,47 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
const Name& zone_name,
boost::function<void(LoadCallback)> rrset_installer)
{
- SegmentObjectHolder<ZoneData, RRClass> holder(
- mem_sgmt, ZoneData::create(mem_sgmt, zone_name), rrclass);
-
- ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
- rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader, _1));
- // Add any last RRsets that were left
- loader.flushNodeRRsets();
-
- const ZoneNode* origin_node = holder.get()->getOriginNode();
- const RdataSet* rdataset = origin_node->getData();
- // If the zone is NSEC3-signed, check if it has NSEC3PARAM
- if (holder.get()->isNSEC3Signed()) {
- if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
- LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
- arg(zone_name).arg(rrclass);
+ while (true) { // Try as long as it takes to load and grow the segment
+ bool created = false;
+ try {
+ SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, rrclass);
+ holder.set(ZoneData::create(mem_sgmt, zone_name));
+
+ // Nothing from this point on should throw MemorySegmentGrown.
+ // It is handled inside here.
+ created = true;
+
+ ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
+ rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader,
+ _1));
+ // Add any last RRsets that were left
+ loader.flushNodeRRsets();
+
+ const ZoneNode* origin_node = holder.get()->getOriginNode();
+ const RdataSet* rdataset = origin_node->getData();
+ // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+ if (holder.get()->isNSEC3Signed()) {
+ if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
+ LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
+ arg(zone_name).arg(rrclass);
+ }
+ }
+
+ RRsetCollection collection(*(holder.get()), rrclass);
+ const dns::ZoneCheckerCallbacks
+ callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
+ boost::bind(&logWarning, &zone_name, &rrclass, _1));
+ if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
+ isc_throw(ZoneValidationError,
+ "Errors found when validating zone: "
+ << zone_name << "/" << rrclass);
+ }
+
+ return (holder.release());
+ } catch (const util::MemorySegmentGrown&) {
+ assert(!created);
}
}
-
- RRsetCollection collection(*(holder.get()), rrclass);
- const dns::ZoneCheckerCallbacks
- callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
- boost::bind(&logWarning, &zone_name, &rrclass, _1));
- if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
- isc_throw(ZoneValidationError,
- "Errors found when validating zone: "
- << zone_name << "/" << rrclass);
- }
-
- return (holder.release());
}
// A wrapper for dns::MasterLoader used by loadZoneData() below. Essentially
@@ -256,7 +268,7 @@ loadZoneData(util::MemorySegment& mem_sgmt,
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_FILE).
arg(zone_name).arg(rrclass).arg(zone_file);
- return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
+ return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(masterLoaderWrapper,
zone_file.c_str(),
zone_name, rrclass,
diff --git a/src/lib/datasrc/memory/zone_data_loader.h b/src/lib/datasrc/memory/zone_data_loader.h
index 32ed58b..56e1ada 100644
--- a/src/lib/datasrc/memory/zone_data_loader.h
+++ b/src/lib/datasrc/memory/zone_data_loader.h
@@ -57,7 +57,7 @@ ZoneData* loadZoneData(util::MemorySegment& mem_sgmt,
/// \c iterator.
///
/// Throws \c ZoneDataUpdater::AddError if invalid or inconsistent data
-/// is present in the \c zone_file. Throws \c isc::Unexpected if empty
+/// is present in the \c iterator. Throws \c isc::Unexpected if empty
/// RRsets are passed by the zone iterator. Throws \c EmptyZone if an
/// empty zone would be created due to the \c loadZoneData().
///
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 4d7e7e0..a8a88e6 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -49,14 +49,14 @@ ZoneDataUpdater::addWildcards(const Name& name) {
// Ensure a separate level exists for the "wildcarding"
// name, and mark the node as "wild".
ZoneNode* node;
- zone_data_.insertName(mem_sgmt_, wname.split(1), &node);
+ zone_data_->insertName(mem_sgmt_, wname.split(1), &node);
node->setFlag(ZoneData::WILDCARD_NODE);
// Ensure a separate level exists for the wildcard name.
// Note: for 'name' itself we do this later anyway, but the
// overhead should be marginal because wildcard names should
// be rare.
- zone_data_.insertName(mem_sgmt_, wname, &node);
+ zone_data_->insertName(mem_sgmt_, wname, &node);
}
}
}
@@ -210,7 +210,7 @@ ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
const NSEC3Hash*
ZoneDataUpdater::getNSEC3Hash() {
if (hash_ == NULL) {
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
// This should never be NULL in this codepath.
assert(nsec3_data != NULL);
@@ -231,11 +231,11 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
dynamic_cast<const T&>(
rrset->getRdataIterator()->getCurrent());
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata);
- zone_data_.setNSEC3Data(nsec3_data);
- zone_data_.setSigned(true);
+ zone_data_->setNSEC3Data(nsec3_data);
+ zone_data_->setSigned(true);
} else {
const NSEC3Hash* hash = getNSEC3Hash();
if (!hash->match(nsec3_rdata)) {
@@ -247,14 +247,14 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
}
void
-ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
- const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr& rrset,
+ const ConstRRsetPtr& rrsig)
{
if (rrset) {
setupNSEC3<generic::NSEC3>(rrset);
}
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
// This is some tricky case: an RRSIG for NSEC3 is given without the
// covered NSEC3, and we don't even know any NSEC3 related data.
@@ -283,14 +283,14 @@ ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
void
ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
- const ConstRRsetPtr rrset,
- const ConstRRsetPtr rrsig)
+ const ConstRRsetPtr& rrset,
+ const ConstRRsetPtr& rrsig)
{
if (rrtype == RRType::NSEC3()) {
addNSEC3(name, rrset, rrsig);
} else {
ZoneNode* node;
- zone_data_.insertName(mem_sgmt_, name, &node);
+ zone_data_->insertName(mem_sgmt_, name, &node);
RdataSet* rdataset_head = node->getData();
@@ -334,7 +334,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// Ok, we just put it in.
// Convenient (and more efficient) shortcut to check RRsets at origin
- const bool is_origin = (node == zone_data_.getOriginNode());
+ const bool is_origin = (node == zone_data_->getOriginNode());
// If this RRset creates a zone cut at this node, mark the node
// indicating the need for callback in find(). Note that we do this
@@ -356,7 +356,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// (conceptually "signed" is a broader notion but our
// current zone finder implementation regards "signed" as
// "NSEC signed")
- zone_data_.setSigned(true);
+ zone_data_->setSigned(true);
}
// If we are adding a new SOA at the origin, update zone's min TTL.
@@ -365,7 +365,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// this should be only once in normal cases) update the TTL.
if (rrset && rrtype == RRType::SOA() && is_origin) {
// Our own validation ensures the RRset is not empty.
- zone_data_.setMinTTL(
+ zone_data_->setMinTTL(
dynamic_cast<const generic::SOA&>(
rrset->getRdataIterator()->getCurrent()).getMinimum());
}
@@ -373,6 +373,24 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
}
void
+ZoneDataUpdater::addInternal(const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig)
+{
+ // Add wildcards possibly contained in the owner name to the domain
+ // tree. This can only happen for the normal (non-NSEC3) tree.
+ // Note: this can throw an exception, breaking strong exception
+ // guarantee. (see also the note for the call to contextCheck()
+ // above).
+ if (rrtype != RRType::NSEC3()) {
+ addWildcards(name);
+ }
+
+ addRdataSet(name, rrtype, rrset, rrsig);
+}
+
+void
ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
const ConstRRsetPtr& sig_rrset)
{
@@ -397,16 +415,22 @@ ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
arg(zone_name_);
- // Add wildcards possibly contained in the owner name to the domain
- // tree. This can only happen for the normal (non-NSEC3) tree.
- // Note: this can throw an exception, breaking strong exception
- // guarantee. (see also the note for the call to contextCheck()
- // above).
- if (rrtype != RRType::NSEC3()) {
- addWildcards(name);
- }
-
- addRdataSet(name, rrtype, rrset, sig_rrset);
+ // Store the address, it may change during growth and the address inside
+ // would get updated.
+ bool added = false;
+ do {
+ try {
+ addInternal(name, rrtype, rrset, sig_rrset);
+ added = true;
+ } catch (const isc::util::MemorySegmentGrown&) {
+ // The segment has grown. So, we update the base pointer (because
+ // the data may have been remapped somewhere else in the process).
+ zone_data_ =
+ static_cast<ZoneData*>(
+ mem_sgmt_.getNamedAddress("updater_zone_data").second);
+ }
+ // Retry if it didn't add due to the growth
+ } while (!added);
}
} // namespace memory
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index 9d669a0..e8826bd 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -57,14 +57,15 @@ public:
/// The constructor.
///
- /// \throw none
- ///
/// \param mem_sgmt The memory segment used for the zone data.
/// \param rrclass The RRclass of the zone data.
/// \param zone_name The Name of the zone under which records will be
/// added.
- // \param zone_data The ZoneData object which is populated with
- // record data.
+ /// \param zone_data The ZoneData object which is populated with
+ /// record data.
+ /// \throw InvalidOperation if there's already a zone data updater
+ /// on the given memory segment. Currently, at most one zone data
+ /// updater may exist on the same memory segment.
ZoneDataUpdater(util::MemorySegment& mem_sgmt,
isc::dns::RRClass rrclass,
const isc::dns::Name& zone_name,
@@ -72,12 +73,25 @@ public:
mem_sgmt_(mem_sgmt),
rrclass_(rrclass),
zone_name_(zone_name),
- zone_data_(zone_data),
- hash_(NULL)
- {}
+ hash_(NULL),
+ zone_data_(&zone_data)
+ {
+ if (mem_sgmt_.getNamedAddress("updater_zone_data").first) {
+ isc_throw(isc::InvalidOperation, "A ZoneDataUpdater already exists"
+ " on this memory segment. Destroy it first.");
+ }
+ if (mem_sgmt_.setNamedAddress("updater_zone_data", zone_data_)) {
+ // It might have relocated during the set
+ zone_data_ =
+ static_cast<ZoneData*>(mem_sgmt_.getNamedAddress(
+ "updater_zone_data").second);
+ }
+ assert(zone_data_);
+ }
/// The destructor.
~ZoneDataUpdater() {
+ mem_sgmt_.clearNamedAddress("updater_zone_data");
delete hash_;
}
@@ -159,6 +173,11 @@ private:
// contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
void addWildcards(const isc::dns::Name& name);
+ void addInternal(const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
+
// Does some checks in context of the data that are already in the
// zone. Currently checks for forbidden combinations of RRsets in
// the same domain (CNAME+anything, DNAME+NS). If such condition is
@@ -175,19 +194,19 @@ private:
template <typename T>
void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
void addNSEC3(const isc::dns::Name& name,
- const isc::dns::ConstRRsetPtr rrset,
- const isc::dns::ConstRRsetPtr rrsig);
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
void addRdataSet(const isc::dns::Name& name,
const isc::dns::RRType& rrtype,
- const isc::dns::ConstRRsetPtr rrset,
- const isc::dns::ConstRRsetPtr rrsig);
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
util::MemorySegment& mem_sgmt_;
const isc::dns::RRClass rrclass_;
const isc::dns::Name& zone_name_;
- ZoneData& zone_data_;
RdataEncoder encoder_;
const isc::dns::NSEC3Hash* hash_;
+ ZoneData* zone_data_;
};
} // namespace memory
diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc
index 77071f4..e1b9baf 100644
--- a/src/lib/datasrc/memory/zone_table.cc
+++ b/src/lib/datasrc/memory/zone_table.cc
@@ -18,6 +18,8 @@
#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/logger.h>
+#include <exceptions/exceptions.h>
+
#include <util/memory_segment.h>
#include <dns/name.h>
@@ -40,7 +42,10 @@ void
deleteZoneData(util::MemorySegment* mem_sgmt, ZoneData* zone_data,
RRClass rrclass)
{
- if (zone_data != NULL) {
+ // We shouldn't delete empty zone data here; the only empty zone
+ // that can be passed here is the placeholder for broken zones maintained
+ // in the zone table. It will stay there until the table is destroyed.
+ if (zone_data && !zone_data->isEmpty()) {
ZoneData::destroy(*mem_sgmt, zone_data, rrclass);
}
}
@@ -49,21 +54,30 @@ typedef boost::function<void(ZoneData*)> ZoneDataDeleterType;
ZoneTable*
ZoneTable::create(util::MemorySegment& mem_sgmt, const RRClass& zone_class) {
- SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> holder(
- mem_sgmt, ZoneTableTree::create(mem_sgmt),
- boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ // Create a placeholder "null" zone data
+ SegmentObjectHolder<ZoneData, RRClass> zdholder(mem_sgmt, zone_class);
+ zdholder.set(ZoneData::create(mem_sgmt));
+
+ // create and setup the tree for the table.
+ SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> tree_holder(
+ mem_sgmt, boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ tree_holder.set(ZoneTableTree::create(mem_sgmt));
void* p = mem_sgmt.allocate(sizeof(ZoneTable));
- ZoneTable* zone_table = new(p) ZoneTable(zone_class, holder.get());
- holder.release();
+
+ // Build zone table with the created objects. Its constructor doesn't
+ // throw, so we can release them from the holder at this point.
+ ZoneTable* zone_table = new(p) ZoneTable(zone_class, tree_holder.release(),
+ zdholder.release());
return (zone_table);
}
void
-ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable)
-{
+ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable) {
ZoneTableTree::destroy(mem_sgmt, ztable->zones_.get(),
boost::bind(deleteZoneData, &mem_sgmt, _1,
ztable->rrclass_));
+ ZoneData::destroy(mem_sgmt, ztable->null_zone_data_.get(),
+ ztable->rrclass_);
mem_sgmt.deallocate(ztable, sizeof(ZoneTable));
}
@@ -74,11 +88,33 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
arg(zone_name).arg(rrclass_);
- if (content == NULL) {
- isc_throw(isc::BadValue, "Zone content must not be NULL");
+ if (!content || content->isEmpty()) {
+ isc_throw(InvalidParameter,
+ (content ? "empty data" : "NULL") <<
+ " is passed to Zone::addZone");
}
- SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, content,
- zone_class);
+ SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, zone_class);
+ holder.set(content);
+
+ const AddResult result =
+ addZoneInternal(mem_sgmt, zone_name, holder.get());
+ holder.release();
+ return (result);
+}
+
+ZoneTable::AddResult
+ZoneTable::addEmptyZone(util::MemorySegment& mem_sgmt, const Name& zone_name) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_EMPTY_ZONE).
+ arg(zone_name).arg(rrclass_);
+
+ return (addZoneInternal(mem_sgmt, zone_name, null_zone_data_.get()));
+}
+
+ZoneTable::AddResult
+ZoneTable::addZoneInternal(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name,
+ ZoneData* content)
+{
// Get the node where we put the zone
ZoneTableNode* node(NULL);
switch (zones_->insert(mem_sgmt, zone_name, &node)) {
@@ -93,10 +129,9 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
// Can Not Happen
assert(node != NULL);
- // We can release now, setData never throws
- ZoneData* old = node->setData(holder.release());
+ ZoneData* old = node->setData(content);
if (old != NULL) {
- return (AddResult(result::EXIST, old));
+ return (AddResult(result::EXIST, old->isEmpty() ? NULL : old));
} else {
++zone_count_;
return (AddResult(result::SUCCESS, NULL));
@@ -126,10 +161,17 @@ ZoneTable::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, NULL));
}
- // Can Not Happen (remember, NOTFOUND is handled)
+ // Can Not Happen (remember, NOTFOUND is handled). node should also have
+ // data because the tree is constructed in the way empty nodes would
+ // be "invisible" for find().
assert(node != NULL);
- return (FindResult(my_result, node->getData()));
+ const ZoneData* zone_data = node->getData();
+ assert(zone_data);
+ const result::ResultFlags flags =
+ zone_data->isEmpty() ? result::ZONE_EMPTY : result::FLAGS_DEFAULT;
+ return (FindResult(my_result, zone_data->isEmpty() ? NULL : zone_data,
+ flags));
}
} // end of namespace memory
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index 2774df3..d092467 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -84,12 +84,16 @@ public:
};
/// \brief Result data of findZone() method.
+ ///
+ /// See \c findZone() about the semantics of the members.
struct FindResult {
FindResult(result::Result param_code,
- const ZoneData* param_zone_data) :
- code(param_code), zone_data(param_zone_data)
+ const ZoneData* param_zone_data,
+ result::ResultFlags param_flags = result::FLAGS_DEFAULT) :
+ code(param_code), flags(param_flags), zone_data(param_zone_data)
{}
const result::Result code;
+ const result::ResultFlags flags;
const ZoneData* const zone_data;
};
@@ -99,13 +103,13 @@ private:
/// An object of this class is always expected to be created by the
/// allocator (\c create()), so the constructor is hidden as private.
///
- /// This constructor internally involves resource allocation, and if
- /// it fails, a corresponding standard exception will be thrown.
- /// It never throws an exception otherwise.
- ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones) :
+ /// This constructor never throws.
+ ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones,
+ ZoneData* null_zone_data) :
rrclass_(rrclass),
zone_count_(0),
- zones_(zones)
+ zones_(zones),
+ null_zone_data_(null_zone_data)
{}
public:
@@ -115,6 +119,15 @@ public:
/// from the given memory segment, constructs the object, and returns
/// a pointer to it.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -145,10 +158,20 @@ public:
/// \throw None.
size_t getZoneCount() const { return (zone_count_); }
- /// Add a new zone to the \c ZoneTable.
+ /// \brief Add a new zone to the \c ZoneTable.
///
/// This method adds a given zone data to the internal table.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw InvalidParameter content is NULL or empty.
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param mem_sgmt The \c MemorySegment to allocate zone data to be
@@ -158,20 +181,43 @@ public:
/// \param zone_class The RR class of the zone. It must be the RR class
/// that is supposed to be associated to the zone table.
/// \param content This one should hold the zone content (the ZoneData).
- /// The ownership is passed onto the zone table. Must not be null.
- /// Must correspond to the name and class and must be allocated from
- /// mem_sgmt.
+ /// The ownership is passed onto the zone table. Must not be null or
+ /// empty. Must correspond to the name and class and must be allocated
+ /// from mem_sgmt.
/// \return \c result::SUCCESS If the zone is successfully
/// added to the zone table.
/// \return \c result::EXIST The zone table already contained
/// zone of the same origin. The old data is replaced and returned
- /// inside the result.
+ /// inside the result unless it's empty; if the zone was previously
+ /// added by \c addEmptyZone(), the data returned is NULL.
AddResult addZone(util::MemorySegment& mem_sgmt,
dns::RRClass zone_class,
const dns::Name& zone_name,
ZoneData* content);
- /// Find a zone that best matches the given name in the \c ZoneTable.
+ /// \brief Add an empty zone to the \c ZoneTable.
+ ///
+ /// This method is similar to \c addZone(), but adds a conceptual "empty"
+ /// zone of the given zone name to the table. The added empty zone
+ /// affects subsequent calls to \c addZone() (and \c addEmptyZone() itself)
+ /// and \c findZone() as described for these methods.
+ ///
+ /// The intended meaning of an empty zone in the table is that the zone
+ /// is somehow broken, such as configured to be loaded but loading failed.
+ /// But this class is not aware of such interpretation; it's up to the
+ /// user of the class how to use the concept of empty zones.
+ ///
+ /// It returns an \c AddResult object as described for \c addZone().
+ ///
+ /// The same notes on exception safety as that for \c addZone() applies.
+ ///
+ /// \param mem_sgmt Same as addZone().
+ /// \param zone_name Same as addZone().
+ AddResult addEmptyZone(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name);
+
+ /// \brief Find a zone that best matches the given name in the
+ /// \c ZoneTable.
///
/// It searches the internal storage for a zone that gives the
/// longest match against \c name, and returns the result in the
@@ -182,8 +228,11 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
/// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
- /// - \c zone_data: corresponding zone data of the found zone; NULL if
- /// no matching zone is found.
+ /// - \c flags If the zone is empty (added by \c addEmptyZone()),
+ /// result::ZONE_EMPTY is set.
+ /// - \c zone_data: corresponding zone data of the found zone if found and
+ /// non empty; NULL if no matching zone is found or the found zone is
+ /// empty.
///
/// \throw none
///
@@ -195,6 +244,18 @@ private:
const dns::RRClass rrclass_;
size_t zone_count_;
boost::interprocess::offset_ptr<ZoneTableTree> zones_;
+
+ // this is a shared placeholder for broken zones
+ boost::interprocess::offset_ptr<ZoneData> null_zone_data_;
+
+ // Common routine for addZone and addEmptyZone. This method can throw
+ // util::MemorySegmentGrown, in which case addresses from mem_sgmt
+ // can be relocated. The caller is responsible for destroying content
+ // on exception, if it needs to be destroyed. On successful return it
+ // ensures there's been no address relocation.
+ AddResult addZoneInternal(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name,
+ ZoneData* content);
};
}
}
diff --git a/src/lib/datasrc/memory/zone_writer.cc b/src/lib/datasrc/memory/zone_writer.cc
index 3cb646a..bc70ffb 100644
--- a/src/lib/datasrc/memory/zone_writer.cc
+++ b/src/lib/datasrc/memory/zone_writer.cc
@@ -16,6 +16,8 @@
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/exceptions.h>
+
#include <memory>
using std::auto_ptr;
@@ -27,13 +29,15 @@ namespace memory {
ZoneWriter::ZoneWriter(ZoneTableSegment& segment,
const LoadAction& load_action,
const dns::Name& origin,
- const dns::RRClass& rrclass) :
+ const dns::RRClass& rrclass,
+ bool throw_on_load_error) :
segment_(segment),
load_action_(load_action),
origin_(origin),
rrclass_(rrclass),
zone_data_(NULL),
- state_(ZW_UNUSED)
+ state_(ZW_UNUSED),
+ catch_load_error_(throw_on_load_error)
{
if (!segment.isWritable()) {
isc_throw(isc::InvalidOperation,
@@ -48,20 +52,30 @@ ZoneWriter::~ZoneWriter() {
}
void
-ZoneWriter::load() {
+ZoneWriter::load(std::string* error_msg) {
if (state_ != ZW_UNUSED) {
isc_throw(isc::InvalidOperation, "Trying to load twice");
}
- zone_data_ = load_action_(segment_.getMemorySegment());
-
- if (!zone_data_) {
- // Bug inside load_action_.
- isc_throw(isc::InvalidOperation, "No data returned from load action");
+ try {
+ zone_data_ = load_action_(segment_.getMemorySegment());
+ segment_.resetHeader();
+
+ if (!zone_data_) {
+ // Bug inside load_action_.
+ isc_throw(isc::InvalidOperation,
+ "No data returned from load action");
+ }
+ } catch (const ZoneLoaderException& ex) {
+ if (!catch_load_error_) {
+ throw;
+ }
+ if (error_msg) {
+ *error_msg = ex.what();
+ }
+ segment_.resetHeader();
}
- segment_.resetHeader();
-
state_ = ZW_LOADED;
}
@@ -71,18 +85,29 @@ ZoneWriter::install() {
isc_throw(isc::InvalidOperation, "No data to install");
}
- ZoneTable* table(segment_.getHeader().getTable());
- if (!table) {
- isc_throw(isc::Unexpected, "No zone table present");
+ // Check the internal integrity assumption: we should have non NULL
+ // zone data or we've allowed load error to create an empty zone.
+ assert(zone_data_ || catch_load_error_);
+
+ // FIXME: This retry is currently untested, as there seems to be no
+ // reasonable way to create a zone table segment with non-local memory
+ // segment. Once there is, we should provide the test.
+ while (state_ != ZW_INSTALLED) {
+ try {
+ ZoneTable* table(segment_.getHeader().getTable());
+ if (table == NULL) {
+ isc_throw(isc::Unexpected, "No zone table present");
+ }
+ const ZoneTable::AddResult result(
+ zone_data_ ? table->addZone(segment_.getMemorySegment(),
+ rrclass_, origin_, zone_data_) :
+ table->addEmptyZone(segment_.getMemorySegment(), origin_));
+ state_ = ZW_INSTALLED;
+ zone_data_ = result.zone_data;
+ } catch (const isc::util::MemorySegmentGrown&) {}
+
+ segment_.resetHeader();
}
- const ZoneTable::AddResult result(table->addZone(
- segment_.getMemorySegment(),
- rrclass_, origin_, zone_data_));
-
- state_ = ZW_INSTALLED;
- zone_data_ = result.zone_data;
-
- segment_.resetHeader();
}
void
diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h
index 1c4e944..554814d 100644
--- a/src/lib/datasrc/memory/zone_writer.h
+++ b/src/lib/datasrc/memory/zone_writer.h
@@ -42,15 +42,27 @@ class ZoneWriter {
public:
/// \brief Constructor
///
+ /// If \c catch_load_error is set to true, the \c load() method will
+ /// internally catch load related errors reported as a DataSourceError
+ /// exception, and subsequent \c install() method will add a special
+ /// empty zone to the zone table segment. If it's set to false, \c load()
+ /// will simply propagate the exception. This parameter would normally
+ /// be set to false as it's not desirable to install a broken zone;
+ /// however, it would be better to be set to true at the initial loading
+ /// so the zone table recognizes the existence of the zone (and being
+ /// aware that it's broken).
+ ///
/// \throw isc::InvalidOperation if \c segment is read-only.
///
/// \param segment The zone table segment to store the zone into.
/// \param load_action The callback used to load data.
/// \param name The name of the zone.
/// \param rrclass The class of the zone.
+ /// \param catch_load_error true if loading errors are to be caught
+ /// internally; false otherwise.
ZoneWriter(ZoneTableSegment& segment,
const LoadAction& load_action, const dns::Name& name,
- const dns::RRClass& rrclass);
+ const dns::RRClass& rrclass, bool catch_load_error);
/// \brief Destructor.
~ZoneWriter();
@@ -64,6 +76,12 @@ public:
/// This is the first method you should call on the object. Never call it
/// multiple times.
///
+ /// If the optional parameter \c error_msg is given and non NULL, and
+ /// if the writer object was constructed with \c catch_load_error being
+ /// true, then error_msg will be filled with text indicating the reason
+ /// for the error in case a load error happens. In other cases any
+ /// passed non NULL error_msg will be intact.
+ ///
/// \note As this contains reading of files or other data sources, or with
/// some other source of the data to load, it may throw quite anything.
/// If it throws, do not call any other methods on the object and
@@ -71,14 +89,23 @@ public:
/// \note After successful load(), you have to call cleanup() some time
/// later.
/// \throw isc::InvalidOperation if called second time.
- void load();
+ /// \throw DataSourceError load related error (not thrown if constructed
+ /// with catch_load_error being false).
+ ///
+ /// \param error_msg If non NULL, used as a placeholder to store load error
+ /// messages.
+ void load(std::string* error_msg = NULL);
/// \brief Put the changes to effect.
///
/// This replaces the old version of zone with the one previously prepared
/// by load(). It takes ownership of the old zone data, if any.
///
- /// You may call it only after successful load() and at most once.
+ /// You may call it only after successful load() and at most once. It
+ /// includes the case the writer is constructed with catch_load_error
+ /// being true and load() encountered and caught a DataSourceError
+ /// exception. In this case this method installs a special empty zone
+ /// to the table.
///
/// The operation is expected to be fast and is meant to be used inside
/// a critical section.
@@ -112,10 +139,15 @@ private:
ZW_CLEANED
};
State state_;
+ const bool catch_load_error_;
};
}
}
}
-#endif
+#endif // MEM_ZONE_WRITER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/result.h b/src/lib/datasrc/result.h
index 5a28d08..7a042cb 100644
--- a/src/lib/datasrc/result.h
+++ b/src/lib/datasrc/result.h
@@ -18,13 +18,10 @@
namespace isc {
namespace datasrc {
namespace result {
-/// Result codes of various public methods of in memory data source
+/// \brief Result codes of various public methods of DataSourceClient.
///
/// The detailed semantics may differ in different methods.
/// See the description of specific methods for more details.
-///
-/// Note: this is intended to be used from other data sources eventually,
-/// but for now it's specific to in memory data source and its backend.
enum Result {
SUCCESS, ///< The operation is successful.
EXIST, ///< The search key is already stored.
@@ -32,8 +29,26 @@ enum Result {
PARTIALMATCH ///< Only a partial match is found.
};
+/// \brief Flags for supplemental information along with the \c Result
+///
+/// Initially there's only one flag defined, but several flags will be added
+/// later. One likely case is to indicate a flag that is listed in in-memory
+/// but its content is served in the underlying data source. This will help
+/// when only a subset of zones are cached in-memory so the lookup code can
+/// efficiently detect whether it doesn't exist or is not just cached.
+/// When more flags are added, the logical-or operation should be allowed
+/// (by defining \c operator|) on these flags.
+enum ResultFlags {
+ FLAGS_DEFAULT = 0, // no flags
+ ZONE_EMPTY = 1 ///< The zone found is empty, normally meaning it's broken
+};
+
}
}
}
-#endif
+#endif // DATASRC_RESULT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index 745654c..58d410b 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/client_list.h>
#include <datasrc/client.h>
#include <datasrc/cache_config.h>
@@ -169,7 +171,7 @@ public:
new memory::ZoneWriter(
*dsrc_info.ztable_segment_,
cache_conf->getLoadAction(rrclass_, zone),
- zone, rrclass_));
+ zone, rrclass_, false));
writer->load();
writer->install();
writer->cleanup(); // not absolutely necessary, but just in case
@@ -206,6 +208,18 @@ public:
EXPECT_EQ(dsrc.get(), result.dsrc_client_);
}
}
+
+ // check the result with empty (broken) zones. Right now this can only
+ // happen for in-memory caches.
+ void emptyResult(const ClientList::FindResult& result, bool exact,
+ const char* trace_txt)
+ {
+ SCOPED_TRACE(trace_txt);
+ ASSERT_FALSE(result.finder_);
+ EXPECT_EQ(exact, result.exact_match_);
+ EXPECT_TRUE(dynamic_cast<InMemoryClient*>(result.dsrc_client_));
+ }
+
// Configure the list with multiple data sources, according to
// some configuration. It uses the index as parameter, to be able to
// loop through the configurations.
@@ -760,7 +774,7 @@ TEST_F(ListTest, masterFiles) {
list_->getDataSources()[0].data_src_client_);
// And it can search
- positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
+ positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
true);
// If cache is not enabled, nothing is loaded
@@ -818,7 +832,8 @@ TEST_F(ListTest, names) {
TEST_F(ListTest, BadMasterFile) {
// Configuration should succeed, and the good zones in the list
- // below should be loaded. No bad zones should be loaded.
+ // below should be loaded. Bad zones won't be "loaded" in its usual sense,
+ // but are still recognized with conceptual "empty" data.
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"MasterFiles\","
@@ -854,13 +869,19 @@ TEST_F(ListTest, BadMasterFile) {
positiveResult(list_->find(Name("example.com."), true), ds_[0],
Name("example.com."), true, "example.com", true);
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.net."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.edu."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.info."), true));
+ // Bad cases: should result in "empty zone", whether the match is exact
+ // or partial.
+ emptyResult(list_->find(Name("foo.bar"), true), true, "foo.bar");
+ emptyResult(list_->find(Name("example.net."), true), true, "example.net");
+ emptyResult(list_->find(Name("example.edu."), true), true, "example.edu");
+ emptyResult(list_->find(Name("example.info."), true), true,
+ "example.info");
+ emptyResult(list_->find(Name("www.example.edu."), false), false,
+ "example.edu, partial");
positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
true);
+ // This one simply doesn't exist.
+ EXPECT_TRUE(list_->find(Name("example.org."), true) == negative_result_);
}
ConfigurableClientList::CacheStatus
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index c3d6a40..fd60af5 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -8,6 +8,14 @@ AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_SHARED_MEMORY
+# See src/lib/util/Makefile.am. We need this because some tests directly
+# use the Boost managed_mapped_file. It's not ideal to deal with it as
+# a module-wide flag, but considering it's for tests and only for limited
+# environments, it would still be better to introduce more complicated setup.
+AM_CXXFLAGS += $(BOOST_MAPPED_FILE_CXXFLAG)
+endif
+
if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 5f52a33..73862e3 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -694,6 +694,15 @@ TEST_F(MemoryClientTest, getIterator) {
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
}
+TEST_F(MemoryClientTest, getIteratorForEmptyZone) {
+ // trying to load a broken zone (zone file not existent). It's internally
+ // stored an empty zone.
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/no-such-file.zone", true);
+ // Then getIterator will result in an exception.
+ EXPECT_THROW(client_->getIterator(Name("example.org")), EmptyZone);
+}
+
TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
TEST_DATA_DIR "/example.org-multiple.zone");
@@ -791,6 +800,35 @@ TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
// Teardown checks for memory segment leaks
}
+TEST_F(MemoryClientTest, findEmptyZone) {
+ // trying to load a broken zone (zone file not existent). It's internally
+ // stored an empty zone.
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/no-such-file.zone", true);
+
+ using namespace isc::datasrc::result;
+
+ // findZone() returns the match, with NULL zone finder and the result
+ // flag indicating it's empty.
+ const DataSourceClient::FindResult result =
+ client_->findZone(Name("example.org"));
+ EXPECT_EQ(SUCCESS, result.code);
+ EXPECT_EQ(ZONE_EMPTY, result.flags);
+ EXPECT_FALSE(result.zone_finder);
+
+ // Same for the case of subdomain match
+ const DataSourceClient::FindResult result_sub =
+ client_->findZone(Name("www.example.org"));
+ EXPECT_EQ(PARTIALMATCH, result_sub.code);
+ EXPECT_EQ(ZONE_EMPTY, result_sub.flags);
+ EXPECT_FALSE(result_sub.zone_finder);
+
+ // findZoneData() will simply NULL (this is for testing only anyway,
+ // so any result would be okay as long as it doesn't cause disruption).
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL),
+ client_->findZoneData(Name("example.org")));
+}
+
TEST_F(MemoryClientTest, findZoneData) {
loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
TEST_DATA_DIR "/example.org-rrsigs.zone");
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index fb01dd6..4e4397e 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -825,7 +825,7 @@ TEST_F(RdataSerializationTest, badAddRdata) {
EXPECT_THROW(encoder_.addRdata(*aaaa_rdata_), std::bad_cast);
const ConstRdataPtr rp_rdata =
- createRdata(RRType::RP(), RRClass::IN(), "a.example. b.example");
+ createRdata(RRType::RP(), RRClass::IN(), "a.example. b.example.");
encoder_.start(RRClass::IN(), RRType::NS());
EXPECT_THROW(encoder_.addRdata(*rp_rdata), std::bad_cast);
diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
index 1a964ef..31e5be2 100644
--- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
@@ -191,20 +191,16 @@ TEST_F(RdataSetTest, mergeCreate) {
SCOPED_TRACE("creating merge case " + lexical_cast<string>(i) +
", " + lexical_cast<string>(j));
// Create old rdataset
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_,
(i & 1) != 0 ? a_rrsets[0] : null_rrset,
- (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset),
- rrclass);
+ (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset));
// Create merged rdataset, based on the old one and RRsets
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_,
(j & 1) != 0 ? a_rrsets[1] : null_rrset,
(j & 2) != 0 ? rrsig_rrsets[1] : null_rrset,
- holder1.get()),
- rrclass);
+ holder1.get()));
// Set up the expected data for the case.
vector<string> expected_rdata;
@@ -241,16 +237,14 @@ TEST_F(RdataSetTest, duplicate) {
// After suppressing duplicates, it should be the same as the default
// RdataSet. Check that.
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig));
checkRdataSet(*holder1.get(), def_rdata_txt_, def_rrsig_txt_);
// Confirm the same thing for the merge mode.
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
- holder1.get()), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
+ holder1.get()));
checkRdataSet(*holder2.get(), def_rdata_txt_, def_rrsig_txt_);
}
@@ -275,24 +269,21 @@ TEST_F(RdataSetTest, getNext) {
TEST_F(RdataSetTest, find) {
// Create some RdataSets and make a chain of them.
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, RRClass::IN());
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
+ ConstRRsetPtr()));
ConstRRsetPtr aaaa_rrset =
textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, RRClass::IN());
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset,
+ ConstRRsetPtr()));
ConstRRsetPtr sigonly_rrset =
textToRRset("www.example.com. 1076895760 IN RRSIG "
"TXT 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKE");
- SegmentObjectHolder<RdataSet, RRClass> holder3(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder3(mem_sgmt_, RRClass::IN());
+ holder3.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
+ sigonly_rrset));
RdataSet* rdataset_a = holder1.get();
RdataSet* rdataset_aaaa = holder2.get();
@@ -376,10 +367,8 @@ TEST_F(RdataSetTest, createManyRRs) {
TEST_F(RdataSetTest, mergeCreateManyRRs) {
ConstRRsetPtr rrset = textToRRset("example.com. 3600 IN TXT some-text");
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()));
checkCreateManyRRs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrset->getRdataCount());
@@ -474,10 +463,8 @@ TEST_F(RdataSetTest, mergeCreateManyRRSIGs) {
ConstRRsetPtr rrsig = textToRRset(
"example.com. 3600 IN RRSIG A 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKEFAKE");
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig),
- rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, rrclass);
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig));
checkCreateManyRRSIGs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrsig->getRdataCount());
@@ -543,12 +530,10 @@ TEST_F(RdataSetTest, badCreate) {
TEST_F(RdataSetTest, badMergeCreate) {
// The 'old RdataSet' for merge. Its content doesn't matter much; the test
// should trigger exception before examining it except for the last checks.
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_,
textToRRset("www.example.com. 0 IN AAAA 2001:db8::1"),
- ConstRRsetPtr()),
- RRClass::IN());
+ ConstRRsetPtr()));
checkBadCreate(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()));
@@ -585,9 +570,8 @@ TEST_F(RdataSetTest, varyingTTL) {
RdataSet::destroy(mem_sgmt_, rdataset, rrclass);
// RRSIG's TTL is smaller
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small));
EXPECT_EQ(RRTTL(10), restoreTTL(holder1.get()->getTTLData()));
// Merging another RRset (w/o sig) that has larger TTL
diff --git a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
index 6329b6b..f3d3934 100644
--- a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
@@ -24,6 +24,8 @@
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace std;
@@ -43,22 +45,24 @@ public:
rrclass("IN"),
origin("example.org"),
zone_file(TEST_DATA_DIR "/rrset-collection.zone"),
- zone_data_holder(mem_sgmt,
- loadZoneData(mem_sgmt, rrclass, origin, zone_file),
- rrclass),
- collection(*zone_data_holder.get(), rrclass)
- {}
+ zone_data_holder(mem_sgmt, rrclass)
+ {
+ zone_data_holder.set(loadZoneData(mem_sgmt, rrclass, origin,
+ zone_file));
+ collection.reset(new RRsetCollection(*zone_data_holder.get(),
+ rrclass));
+ }
const RRClass rrclass;
const Name origin;
std::string zone_file;
test::MemorySegmentMock mem_sgmt;
SegmentObjectHolder<ZoneData, RRClass> zone_data_holder;
- RRsetCollection collection;
+ boost::scoped_ptr<RRsetCollection> collection;
};
TEST_F(RRsetCollectionTest, find) {
- const RRsetCollection& ccln = collection;
+ const RRsetCollection& ccln = *collection;
ConstRRsetPtr rrset = ccln.find(Name("www.example.org"), rrclass,
RRType::A());
EXPECT_TRUE(rrset);
diff --git a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
index d27e364..73aa18b 100644
--- a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
@@ -12,10 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <util/memory_segment_local.h>
+#include <util/memory_segment_mapped.h>
#include <datasrc/memory/segment_object_holder.h>
+#ifdef USE_SHARED_MEMORY
+#include <boost/interprocess/managed_mapped_file.hpp>
+#endif
+
#include <gtest/gtest.h>
using namespace isc::util;
@@ -24,12 +31,14 @@ using namespace isc::datasrc::memory::detail;
namespace {
const int TEST_ARG_VAL = 42; // arbitrary chosen magic number
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
class TestObject {
public:
static void destroy(MemorySegment& sgmt, TestObject* obj, int arg) {
sgmt.deallocate(obj, sizeof(*obj));
EXPECT_EQ(TEST_ARG_VAL, arg);
+ EXPECT_TRUE(obj);
}
};
@@ -42,7 +51,8 @@ useHolder(MemorySegment& sgmt, TestObject* obj, bool release) {
// deallocate().
typedef SegmentObjectHolder<TestObject, int> HolderType;
- HolderType holder(sgmt, obj, TEST_ARG_VAL);
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ holder.set(obj);
EXPECT_EQ(obj, holder.get());
if (release) {
EXPECT_EQ(obj, holder.release());
@@ -64,4 +74,70 @@ TEST(SegmentObjectHolderTest, foo) {
useHolder(sgmt, obj, false);
EXPECT_TRUE(sgmt.allMemoryDeallocated());
}
+
+// Test nothing bad happens if the holder is not set before it is destroyed
+TEST(SegmentObjectHolderTest, destroyNotSet) {
+ MemorySegmentLocal sgmt;
+ {
+ typedef SegmentObjectHolder<TestObject, int> HolderType;
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ }
+ EXPECT_TRUE(sgmt.allMemoryDeallocated());
+}
+
+#ifdef USE_SHARED_MEMORY
+// Keep allocating bigger and bigger chunks of data until the allocation
+// fails with growing the segment.
+void
+allocateUntilGrows(MemorySegment& segment, size_t& current_size) {
+ // Create an object that will not be explicitly deallocated.
+ // It must be deallocated by the segment holder and even in case
+ // the position moved.
+ void *object_memory = segment.allocate(sizeof(TestObject));
+ TestObject* object = new(object_memory) TestObject;
+ SegmentObjectHolder<TestObject, int> holder(segment, TEST_ARG_VAL);
+ holder.set(object);
+ while (true) {
+ void* data = segment.allocate(current_size);
+ segment.deallocate(data, current_size);
+ current_size *= 2;
+ }
+}
+
+// Check that the segment thing releases stuff even in case it throws
+// SegmentGrown exception and the thing moves address
+TEST(SegmentObjectHolderTest, grow) {
+ MemorySegmentMapped segment(mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY);
+ // Allocate a bit of memory, to get a unique address
+ void* mark = segment.allocate(1);
+ segment.setNamedAddress("mark", mark);
+
+ // We'd like to cause 'mark' will be mapped at a different address on
+ // MemorySegmentGrown; there doesn't seem to be a reliable and safe way
+ // to cause this situation, but opening another mapped region seems to
+ // often work in practice. We use Boost managed_mapped_file directly
+ // to ignore the imposed file lock with MemorySegmentMapped.
+ using boost::interprocess::managed_mapped_file;
+ using boost::interprocess::open_only;
+ managed_mapped_file mapped_sgmt(open_only, mapped_file);
+
+ // Try allocating bigger and bigger chunks of data until the segment
+ // actually relocates
+ size_t alloc_size = 1024;
+ EXPECT_THROW(allocateUntilGrows(segment, alloc_size), MemorySegmentGrown);
+ // Confirm it's now mapped at a different address.
+ EXPECT_NE(mark, segment.getNamedAddress("mark").second)
+ << "portability assumption for the test doesn't hold; "
+ "disable the test by setting env variable GTEST_FILTER to "
+ "'-SegmentObjectHolderTest.grow'";
+ mark = segment.getNamedAddress("mark").second;
+ segment.clearNamedAddress("mark");
+ segment.deallocate(mark, 1);
+ EXPECT_TRUE(segment.allMemoryDeallocated());
+ // Remove the file
+ EXPECT_EQ(0, unlink(mapped_file));
+}
+#endif
+
}
diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
index 47ec4d3..ad20d43 100644
--- a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
@@ -16,11 +16,18 @@
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/zone_iterator.h>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+#ifdef USE_SHARED_MEMORY
+#include <util/memory_segment_mapped.h>
+#endif
+#include <util/memory_segment_local.h>
#include <datasrc/tests/memory/memory_segment_mock.h>
@@ -28,6 +35,10 @@
using namespace isc::dns;
using namespace isc::datasrc::memory;
+#ifdef USE_SHARED_MEMORY
+using isc::util::MemorySegmentMapped;
+#endif
+using isc::datasrc::memory::detail::SegmentObjectHolder;
namespace {
@@ -73,4 +84,35 @@ TEST_F(ZoneDataLoaderTest, zoneMinTTL) {
EXPECT_EQ(RRTTL(1200), RRTTL(b));
}
+// Load bunch of small zones, hoping some of the relocation will happen
+// during the memory creation, not only Rdata creation.
+// Note: this doesn't even compile unless USE_SHARED_MEMORY is defined.
+#ifdef USE_SHARED_MEMORY
+TEST(ZoneDataLoaterTest, relocate) {
+ const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+ MemorySegmentMapped segment(mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY,
+ 4096);
+ const size_t zone_count = 10000;
+ typedef SegmentObjectHolder<ZoneData, RRClass> Holder;
+ typedef boost::shared_ptr<Holder> HolderPtr;
+ std::vector<HolderPtr> zones;
+ for (size_t i = 0; i < zone_count; ++i) {
+ // Load some zone
+ ZoneData* data = loadZoneData(segment, RRClass::IN(),
+ Name("example.org"),
+ TEST_DATA_DIR
+ "/example.org-nsec3-signed.zone");
+ // Store it, so it is cleaned up later
+ zones.push_back(HolderPtr(new Holder(segment, RRClass::IN())));
+ zones.back()->set(data);
+
+ }
+ // Deallocate all the zones now.
+ zones.clear();
+ EXPECT_TRUE(segment.allMemoryDeallocated());
+ EXPECT_EQ(0, unlink(mapped_file));
+}
+#endif
+
}
diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
index 983e588..54a0fc4 100644
--- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
@@ -277,4 +277,14 @@ TEST_F(ZoneDataTest, minTTL) {
zone_data_->setMinTTL(1200);
EXPECT_EQ(RRTTL(1200), createRRTTL(zone_data_->getMinTTLData()));
}
+
+TEST_F(ZoneDataTest, emptyData) {
+ // normally create zone data are never "empty"
+ EXPECT_FALSE(zone_data_->isEmpty());
+
+ // zone data instance created by the special create() is made "empty".
+ ZoneData* empty_data = ZoneData::create(mem_sgmt_);
+ EXPECT_TRUE(empty_data->isEmpty());
+ ZoneData::destroy(mem_sgmt_, empty_data, RRClass::IN());
+}
}
diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
index 94870f9..f3e814a 100644
--- a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/zone_data.h>
@@ -25,11 +27,14 @@
#include <dns/rrset.h>
#include <dns/rrttl.h>
+#include <util/memory_segment_local.h>
+#include <util/memory_segment_mapped.h>
#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
#include <cassert>
@@ -39,71 +44,158 @@ using namespace isc::datasrc::memory;
namespace {
-class ZoneDataUpdaterTest : public ::testing::Test {
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+
+// An abstract factory class for the segments. We want fresh segment for each
+// test, so we have different factories for them.
+class SegmentCreator {
+public:
+ virtual ~SegmentCreator() {}
+ typedef boost::shared_ptr<isc::util::MemorySegment> SegmentPtr;
+ // Create the segment.
+ virtual SegmentPtr create() const = 0;
+ // Clean-up after the test. Most of them will be just NOP (the default),
+ // but the file-mapped one needs to remove the file.
+ virtual void cleanup() const {}
+};
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+ ZoneData* zone_data)
+{
+ ZoneNode* node = NULL;
+ zone_data->insertName(mem_sgmt, name, &node);
+ EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+ return (node);
+}
+
+class ZoneDataUpdaterTest : public ::testing::TestWithParam<SegmentCreator*> {
protected:
ZoneDataUpdaterTest() :
zname_("example.org"), zclass_(RRClass::IN()),
- zone_data_(ZoneData::create(mem_sgmt_, zname_)),
- updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
- {}
+ mem_sgmt_(GetParam()->create())
+ {
+ ZoneData* data = ZoneData::create(*mem_sgmt_, zname_);
+ mem_sgmt_->setNamedAddress("Test zone data", data);
+ updater_.reset(new ZoneDataUpdater(*mem_sgmt_, zclass_, zname_,
+ *data));
+ }
~ZoneDataUpdaterTest() {
- if (zone_data_ != NULL) {
- ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
- }
- if (!mem_sgmt_.allMemoryDeallocated()) {
+ assert(updater_);
+ ZoneData::destroy(*mem_sgmt_, getZoneData(), zclass_);
+ // Release the updater, so it frees all memory inside the segment too
+ updater_.reset();
+ mem_sgmt_->clearNamedAddress("Test zone data");
+ if (!mem_sgmt_->allMemoryDeallocated()) {
ADD_FAILURE() << "Memory leak detected";
}
+ GetParam()->cleanup();
}
void clearZoneData() {
- assert(zone_data_ != NULL);
- ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
- zone_data_ = ZoneData::create(mem_sgmt_, zname_);
- updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
- *zone_data_));
+ assert(updater_);
+ ZoneData::destroy(*mem_sgmt_, getZoneData(), zclass_);
+ mem_sgmt_->clearNamedAddress("Test zone data");
+ updater_.reset();
+ ZoneData* data = ZoneData::create(*mem_sgmt_, zname_);
+ mem_sgmt_->setNamedAddress("Test zone data", data);
+ updater_.reset(new ZoneDataUpdater(*mem_sgmt_, zclass_, zname_,
+ *data));
+ }
+
+ ZoneData* getZoneData() {
+ return (static_cast<ZoneData*>(
+ mem_sgmt_->getNamedAddress("Test zone data").second));
}
const Name zname_;
const RRClass zclass_;
- test::MemorySegmentMock mem_sgmt_;
- ZoneData* zone_data_;
+ boost::shared_ptr<isc::util::MemorySegment> mem_sgmt_;
boost::scoped_ptr<ZoneDataUpdater> updater_;
};
-TEST_F(ZoneDataUpdaterTest, bothNull) {
+class TestSegmentCreator : public SegmentCreator {
+public:
+ virtual SegmentPtr create() const {
+ return (SegmentPtr(new test::MemorySegmentMock));
+ }
+};
+
+TestSegmentCreator test_segment_creator;
+
+INSTANTIATE_TEST_CASE_P(TestSegment, ZoneDataUpdaterTest,
+ ::testing::Values(static_cast<SegmentCreator*>(
+ &test_segment_creator)));
+
+class MemorySegmentCreator : public SegmentCreator {
+public:
+ virtual SegmentPtr create() const {
+ // We are not really supposed to create the segment directly in real
+ // code, but it should be OK inside tests.
+ return (SegmentPtr(new isc::util::MemorySegmentLocal));
+ }
+};
+
+MemorySegmentCreator memory_segment_creator;
+
+INSTANTIATE_TEST_CASE_P(LocalSegment, ZoneDataUpdaterTest,
+ ::testing::Values(static_cast<SegmentCreator*>(
+ &memory_segment_creator)));
+
+class MappedSegmentCreator : public SegmentCreator {
+public:
+ MappedSegmentCreator(size_t initial_size =
+ isc::util::MemorySegmentMapped::INITIAL_SIZE) :
+ initial_size_(initial_size)
+ {}
+ virtual SegmentPtr create() const {
+ return (SegmentPtr(new isc::util::MemorySegmentMapped(
+ mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY,
+ initial_size_)));
+ }
+ virtual void cleanup() const {
+ EXPECT_EQ(0, unlink(mapped_file));
+ }
+private:
+ const size_t initial_size_;
+};
+
+#ifdef USE_SHARED_MEMORY
+// There should be no initialization fiasco there. We only set int value inside
+// and don't use it until the create() is called.
+MappedSegmentCreator small_creator(4092), default_creator;
+
+INSTANTIATE_TEST_CASE_P(MappedSegment, ZoneDataUpdaterTest, ::testing::Values(
+ static_cast<SegmentCreator*>(&small_creator),
+ static_cast<SegmentCreator*>(&default_creator)));
+#endif
+
+TEST_P(ZoneDataUpdaterTest, bothNull) {
// At least either covered RRset or RRSIG must be non NULL.
EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
ZoneDataUpdater::NullRRset);
}
-ZoneNode*
-getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
- ZoneData* zone_data)
-{
- ZoneNode* node = NULL;
- zone_data->insertName(mem_sgmt, name, &node);
- EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
- return (node);
-}
-
-TEST_F(ZoneDataUpdaterTest, zoneMinTTL) {
+TEST_P(ZoneDataUpdaterTest, zoneMinTTL) {
// If we add SOA, zone's min TTL will be updated.
updater_->add(textToRRset(
"example.org. 3600 IN SOA . . 0 0 0 0 1200",
zclass_, zname_),
ConstRRsetPtr());
- isc::util::InputBuffer b(zone_data_->getMinTTLData(), sizeof(uint32_t));
+ isc::util::InputBuffer b(getZoneData()->getMinTTLData(), sizeof(uint32_t));
EXPECT_EQ(RRTTL(1200), RRTTL(b));
}
-TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+TEST_P(ZoneDataUpdaterTest, rrsigOnly) {
// RRSIG that doesn't have covered RRset can be added. The resulting
// rdataset won't have "normal" RDATA but sig RDATA.
updater_->add(ConstRRsetPtr(), textToRRset(
"www.example.org. 3600 IN RRSIG A 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+ ZoneNode* node = getNode(*mem_sgmt_, Name("www.example.org"),
+ getZoneData());
const RdataSet* rdset = node->getData();
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
rdset = RdataSet::find(rdset, RRType::A(), true);
@@ -121,7 +213,7 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
updater_->add(ConstRRsetPtr(), textToRRset(
"*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("wild.example.org"), getZoneData());
EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
// Simply adding RRSIG covering (delegating NS) shouldn't enable callback
@@ -129,14 +221,14 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
updater_->add(ConstRRsetPtr(), textToRRset(
"child.example.org. 3600 IN RRSIG NS 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("child.example.org"), getZoneData());
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
// Same for DNAME
updater_->add(ConstRRsetPtr(), textToRRset(
"dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("dname.example.org"), getZoneData());
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
// Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
@@ -144,13 +236,13 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
updater_->add(ConstRRsetPtr(), textToRRset(
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_FALSE(zone_data_->isNSEC3Signed());
+ EXPECT_FALSE(getZoneData()->isNSEC3Signed());
// And same for (RRSIG for) NSEC and "is signed".
updater_->add(ConstRRsetPtr(), textToRRset(
"example.org. 3600 IN RRSIG NSEC 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_FALSE(zone_data_->isSigned());
+ EXPECT_FALSE(getZoneData()->isSigned());
}
// Commonly used checks for rrsigForNSEC3Only
@@ -168,7 +260,7 @@ checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
EXPECT_EQ(1, rdset->getSigRdataCount());
}
-TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+TEST_P(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
// Adding only RRSIG covering NSEC3 is tricky. It should go to the
// separate NSEC3 tree, but the separate space is only created when
// NSEC3 or NSEC3PARAM is added. So, in many cases RRSIG-only is allowed,
@@ -183,12 +275,12 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
textToRRset(
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_TRUE(zone_data_->isNSEC3Signed());
+ EXPECT_TRUE(getZoneData()->isNSEC3Signed());
updater_->add(ConstRRsetPtr(),
textToRRset(
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+ checkNSEC3Rdata(*mem_sgmt_, Name("09GM.example.org"), getZoneData());
// Clear the current content of zone, then add NSEC3
clearZoneData();
@@ -201,7 +293,7 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
textToRRset(
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+ checkNSEC3Rdata(*mem_sgmt_, Name("09GM.example.org"), getZoneData());
// If we add only RRSIG without any NSEC3 related data beforehand,
// it will be rejected; it's a limitation of the current implementation.
@@ -214,4 +306,39 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
isc::NotImplemented);
}
+// Generate many small RRsets. This tests that the underlying memory segment
+// can grow during the execution and that the updater handles that well.
+//
+// Some of the grows will happen inserting the RRSIG, some with the TXT. Or,
+// at least we hope so.
+TEST_P(ZoneDataUpdaterTest, manySmallRRsets) {
+ for (size_t i = 0; i < 32768; ++i) {
+ const std::string name(boost::lexical_cast<std::string>(i) +
+ ".example.org.");
+ updater_->add(textToRRset(name + " 3600 IN TXT " +
+ std::string(30, 'X')),
+ textToRRset(name + " 3600 IN RRSIG TXT 5 3 3600 "
+ "20150420235959 20051021000000 1 "
+ "example.org. FAKE"));
+ ZoneNode* node = getNode(*mem_sgmt_,
+ Name(boost::lexical_cast<std::string>(i) +
+ ".example.org"), getZoneData());
+ const RdataSet* rdset = node->getData();
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ rdset = RdataSet::find(rdset, RRType::TXT(), true);
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ EXPECT_EQ(1, rdset->getRdataCount());
+ EXPECT_EQ(1, rdset->getSigRdataCount());
+ }
+}
+
+TEST_P(ZoneDataUpdaterTest, updaterCollision) {
+ ZoneData* zone_data = ZoneData::create(*mem_sgmt_,
+ Name("another.example.com."));
+ EXPECT_THROW(ZoneDataUpdater(*mem_sgmt_, RRClass::IN(),
+ Name("another.example.com."), *zone_data),
+ isc::InvalidOperation);
+ ZoneData::destroy(*mem_sgmt_, zone_data, RRClass::IN());
+}
+
}
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index be53820..e07ab27 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -98,7 +98,7 @@ protected:
origin_("example.org"),
zone_data_(ZoneData::create(mem_sgmt_, origin_)),
zone_finder_(*zone_data_, class_),
- updater_(mem_sgmt_, class_, origin_, *zone_data_)
+ updater_(new ZoneDataUpdater(mem_sgmt_, class_, origin_, *zone_data_))
{
// Build test RRsets. Below, we construct an RRset for
// each textual RR(s) of zone_data, and assign it to the corresponding
@@ -196,7 +196,7 @@ protected:
}
void addToZoneData(const ConstRRsetPtr rrset) {
- updater_.add(rrset, rrset->getRRsig());
+ updater_->add(rrset, rrset->getRRsig());
}
/// \brief expensive rrset converter
@@ -224,7 +224,7 @@ protected:
MemorySegmentMock mem_sgmt_;
memory::ZoneData* zone_data_;
memory::InMemoryZoneFinder zone_finder_;
- ZoneDataUpdater updater_;
+ boost::scoped_ptr<ZoneDataUpdater> updater_;
// Placeholder for storing RRsets to be checked with rrsetsCheck()
vector<ConstRRsetPtr> actual_rrsets_;
@@ -1549,19 +1549,19 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
// Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
// query for the TXT should result in NXRRSET.
addToZoneData(rr_ns_a_);
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
- "20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+ "20120715220826 1234 example.com. FAKE"));
findTest(Name("ns.example.org"), RRType::TXT(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
// Add RRSIG-only covering NSEC. This shouldn't be returned when NSEC is
// requested, whether it's for NXRRSET or NXDOMAIN
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
// The added RRSIG for NSEC could be used for NXRRSET but shouldn't
findTest(Name("ns.example.org"), RRType::TXT(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
@@ -1572,29 +1572,29 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
// RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("nocname.example.org"), RRType::A(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
// RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
// case explicitly.
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("nodelegation.example.org"), RRType::A(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
findTest(Name("www.nodelegation.example.org"), RRType::A(),
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
// Same for RRSIG-only for DNAME
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("www.nodname.example.org"), RRType::A(),
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
// If we have a delegation NS at this node, it will be a bit trickier,
@@ -1614,6 +1614,7 @@ TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
new ZoneTableSegmentMock(class_, mem_sgmt_));
+ updater_.reset();
loadZoneIntoTable(*ztable_segment, name, class_,
TEST_DATA_DIR "/2504-test.zone");
InMemoryClient client(ztable_segment, class_);
@@ -1758,10 +1759,10 @@ TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
// add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
// should result in an exception.
const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
DataSourceError);
}
@@ -1776,6 +1777,7 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
new ZoneTableSegmentMock(class_, mem_sgmt_));
+ updater_.reset();
loadZoneIntoTable(*ztable_segment, name, class_,
TEST_DATA_DIR "/2503-test.zone");
InMemoryClient client(ztable_segment, class_);
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.cc b/src/lib/datasrc/tests/memory/zone_loader_util.cc
index bd55e66..155871d 100644
--- a/src/lib/datasrc/tests/memory/zone_loader_util.cc
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.cc
@@ -33,7 +33,8 @@ namespace test {
void
loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
- const dns::RRClass& zclass, const std::string& zone_file)
+ const dns::RRClass& zclass, const std::string& zone_file,
+ bool load_error_ok)
{
const isc::datasrc::internal::CacheConfig cache_conf(
"MasterFiles", NULL, *data::Element::fromJSON(
@@ -41,7 +42,7 @@ loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
" \"params\": {\"" + zname.toText() + "\": \"" + zone_file +
"\"}}"), true);
memory::ZoneWriter writer(zt_sgmt, cache_conf.getLoadAction(zclass, zname),
- zname, zclass);
+ zname, zclass, load_error_ok);
writer.load();
writer.install();
writer.cleanup();
@@ -72,7 +73,7 @@ loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
const dns::RRClass& zclass, ZoneIterator& iterator)
{
memory::ZoneWriter writer(zt_sgmt, IteratorLoader(zclass, zname, iterator),
- zname, zclass);
+ zname, zclass, false);
writer.load();
writer.install();
writer.cleanup();
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.h b/src/lib/datasrc/tests/memory/zone_loader_util.h
index 06eba87..6d1f764 100644
--- a/src/lib/datasrc/tests/memory/zone_loader_util.h
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.h
@@ -33,9 +33,14 @@ namespace test {
/// This function does nothing special, simply provides a shortcut for commonly
/// used pattern that would be used in tests with a ZoneTableSegment loading
/// a zone from file into it.
+///
+/// If the optional load_error_ok parameter is set to true, it will create
+/// an internal empty zone in the table when it encounters a loading error.
+/// Otherwise ZoneLoaderException will be thrown in such cases.
void
loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
- const dns::RRClass& zclass, const std::string& zone_file);
+ const dns::RRClass& zclass, const std::string& zone_file,
+ bool load_error_ok = false);
/// \brief A shortcut utility to load a specified zone into ZoneTableSegment
/// from a zone iterator.
diff --git a/src/lib/datasrc/tests/memory/zone_table_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
index 6599f3b..328274f 100644
--- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
@@ -73,13 +73,22 @@ TEST_F(ZoneTableTest, addZone) {
// By default there's no zone contained.
EXPECT_EQ(0, zone_table->getZoneCount());
- // It doesn't accept empty (NULL) zones
+ // It doesn't accept NULL as zone data
EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
- isc::BadValue);
+ isc::InvalidParameter);
EXPECT_EQ(0, zone_table->getZoneCount()); // count is still 0
+ // or an empty zone data
+ SegmentObjectHolder<ZoneData, RRClass> holder_empty(
+ mem_sgmt_, zclass_);
+ holder_empty.set(ZoneData::create(mem_sgmt_));
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1,
+ holder_empty.get()),
+ isc::InvalidParameter);
+
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ holder1.set(ZoneData::create(mem_sgmt_, zname1));
const ZoneData* data1(holder1.get());
// Normal successful case.
const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zclass_,
@@ -91,9 +100,9 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder1.get());
EXPECT_EQ(1, zone_table->getZoneCount()); // count is now incremented
- // Duplicate add doesn't replace the existing data.
- SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ // Duplicate add replaces the existing data wit the newly added one.
+ SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname1));
const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
zname1,
holder2.release()));
@@ -107,8 +116,8 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(1, zone_table->getZoneCount()); // count doesn't change.
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")),
- zclass_);
+ mem_sgmt_, zclass_);
+ holder3.set(ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")));
// names are compared in a case insensitive manner.
const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_, zclass_,
Name("EXAMPLE.COM"),
@@ -117,13 +126,15 @@ TEST_F(ZoneTableTest, addZone) {
ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
// Add some more different ones. Should just succeed.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname2,
holder4.release()).code);
EXPECT_EQ(2, zone_table->getZoneCount());
SegmentObjectHolder<ZoneData, RRClass> holder5(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder5.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname3,
holder5.release()).code);
@@ -133,26 +144,70 @@ TEST_F(ZoneTableTest, addZone) {
// tree. It still shouldn't cause memory leak (which would be detected
// in TearDown()).
SegmentObjectHolder<ZoneData, RRClass> holder6(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("example.org")), zclass_);
+ mem_sgmt_, zclass_);
+ holder6.set(ZoneData::create(mem_sgmt_, Name("example.org")));
mem_sgmt_.setThrowCount(1);
EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org"),
holder6.release()),
std::bad_alloc);
}
+TEST_F(ZoneTableTest, addEmptyZone) {
+ // By default there's no zone contained.
+ EXPECT_EQ(0, zone_table->getZoneCount());
+
+ // Adding an empty zone. It should succeed.
+ const ZoneTable::AddResult result1 =
+ zone_table->addEmptyZone(mem_sgmt_, zname1);
+ EXPECT_EQ(result::SUCCESS, result1.code);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+
+ // The empty zone can be "found", with the ZONE_EMPTY flag on, and the
+ // returned ZoneData being NULL.
+ const ZoneTable::FindResult fresult1 = zone_table->findZone(zname1);
+ EXPECT_EQ(result::SUCCESS, fresult1.code);
+ EXPECT_EQ(result::ZONE_EMPTY, fresult1.flags);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), fresult1.zone_data);
+
+ // Replacing an empty zone with non-empty one. Should be no problem, but
+ // the empty zone data are not returned in the result structure; it's
+ // internal to the ZoneTable implementation.
+ SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname1));
+ const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
+ zname1,
+ holder2.release()));
+ EXPECT_EQ(result::EXIST, result2.code);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), result2.zone_data);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+
+ // Replacing a non-empty zone with an empty one is also okay. It's not
+ // different from replacing with another non-empty one.
+ const ZoneTable::AddResult result3 =
+ zone_table->addEmptyZone(mem_sgmt_, zname1);
+ EXPECT_EQ(result::EXIST, result3.code);
+ EXPECT_NE(static_cast<const ZoneData*>(NULL), result3.zone_data);
+ ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+}
+
TEST_F(ZoneTableTest, findZone) {
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ holder1.set(ZoneData::create(mem_sgmt_, zname1));
ZoneData* zone_data = holder1.get();
EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_, zname1,
holder1.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname2,
holder2.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder3.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname3,
holder3.release()).code);
@@ -177,7 +232,8 @@ TEST_F(ZoneTableTest, findZone) {
// make sure the partial match is indeed the longest match by adding
// a zone with a shorter origin and query again.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("com")), zclass_);
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, Name("com")));
EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
Name("com"),
holder4.release()).code);
diff --git a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
index f4f2295..1838fa9 100644
--- a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
@@ -15,6 +15,7 @@
#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
#include <datasrc/memory/zone_data.h>
+#include <datasrc/exceptions.h>
#include <dns/rrclass.h>
#include <dns/name.h>
@@ -27,10 +28,13 @@
#include <boost/scoped_ptr.hpp>
#include <boost/bind.hpp>
+#include <string>
+
using boost::scoped_ptr;
using boost::bind;
using isc::dns::RRClass;
using isc::dns::Name;
+using isc::datasrc::ZoneLoaderException;
using namespace isc::datasrc::memory;
using namespace isc::datasrc::memory::test;
@@ -60,9 +64,10 @@ protected:
writer_(new
ZoneWriter(*segment_,
bind(&ZoneWriterTest::loadAction, this, _1),
- Name("example.org"), RRClass::IN())),
+ Name("example.org"), RRClass::IN(), false)),
load_called_(false),
load_throw_(false),
+ load_loader_throw_(false),
load_null_(false),
load_data_(false)
{}
@@ -75,6 +80,7 @@ protected:
scoped_ptr<ZoneWriter> writer_;
bool load_called_;
bool load_throw_;
+ bool load_loader_throw_;
bool load_null_;
bool load_data_;
public:
@@ -87,6 +93,9 @@ public:
if (load_throw_) {
throw TestException();
}
+ if (load_loader_throw_) {
+ isc_throw(ZoneLoaderException, "faked loader exception");
+ }
if (load_null_) {
// Be nasty to the caller and return NULL, which is forbidden
@@ -130,7 +139,7 @@ TEST_F(ZoneWriterTest, constructForReadOnlySegment) {
ReadOnlySegment ztable_segment(RRClass::IN(), mem_sgmt);
EXPECT_THROW(ZoneWriter(ztable_segment,
bind(&ZoneWriterTest::loadAction, this, _1),
- Name("example.org"), RRClass::IN()),
+ Name("example.org"), RRClass::IN(), false),
isc::InvalidOperation);
}
@@ -243,6 +252,57 @@ TEST_F(ZoneWriterTest, loadThrows) {
EXPECT_NO_THROW(writer_->cleanup());
}
+// Emulate the situation where load() throws loader error.
+TEST_F(ZoneWriterTest, loadLoaderException) {
+ std::string error_msg;
+
+ // By default, the exception is propagated.
+ load_loader_throw_ = true;
+ EXPECT_THROW(writer_->load(), ZoneLoaderException);
+ // In this case, passed error_msg won't be updated.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), false));
+ EXPECT_THROW(writer_->load(&error_msg), ZoneLoaderException);
+ EXPECT_EQ("", error_msg);
+
+ // If we specify allowing load error, load() will succeed and install()
+ // adds an empty zone. Note that we implicitly pass NULL to load()
+ // as it's the default parameter, so the following also confirms it doesn't
+ // cause disruption.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load();
+ writer_->install();
+ writer_->cleanup();
+
+ // Check an empty zone has been really installed.
+ using namespace isc::datasrc::result;
+ const ZoneTable* ztable = segment_->getHeader().getTable();
+ ASSERT_TRUE(ztable);
+ const ZoneTable::FindResult result = ztable->findZone(Name("example.org"));
+ EXPECT_EQ(SUCCESS, result.code);
+ EXPECT_EQ(ZONE_EMPTY, result.flags);
+
+ // Allowing an error, and passing a template for the error message.
+ // It will be filled with the reason for the error.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load(&error_msg);
+ EXPECT_NE("", error_msg);
+
+ // In case of no error, the placeholder will be intact.
+ load_loader_throw_ = false;
+ error_msg.clear();
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load(&error_msg);
+ EXPECT_EQ("", error_msg);
+}
+
// Check the strong exception guarantee - if it throws, nothing happened
// to the content.
TEST_F(ZoneWriterTest, retry) {
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index fc08305..65e9f6c 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -79,7 +79,7 @@ createInMemoryClient(RRClass zclass, const Name& zname) {
ZoneTableSegment::create(zclass, cache_conf.getSegmentType()));
memory::ZoneWriter writer(*ztable_segment,
cache_conf.getLoadAction(zclass, zname),
- zname, zclass);
+ zname, zclass, false);
writer.load();
writer.install();
writer.cleanup();
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index 80d23b7..cd1999a 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -320,8 +320,9 @@ protected:
if (filename) {
boost::scoped_ptr<memory::ZoneWriter> writer(
new memory::ZoneWriter(*ztable_segment_,
- cache_conf.getLoadAction(rrclass_, zone),
- zone, rrclass_));
+ cache_conf.getLoadAction(rrclass_,
+ zone),
+ zone, rrclass_, false));
writer->load();
writer->install();
writer->cleanup();
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 1e292bd..114c301 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -33,11 +33,17 @@ libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += option_space.cc option_space.h
+libb10_dhcp___la_SOURCES += option_string.cc option_string.h
+libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcp___la_SOURCES += pkt_filter.h
libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+
+if OS_LINUX
libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
+endif
+
libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
index 13a16bf..848ad23 100644
--- a/src/lib/dhcp/hwaddr.h
+++ b/src/lib/dhcp/hwaddr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +27,10 @@ namespace dhcp {
/// @brief Hardware type that represents information from DHCPv4 packet
struct HWAddr {
public:
+
+ /// @brief Size of an ethernet hardware address.
+ static const size_t ETHERNET_HWADDR_LEN = 6;
+
/// @brief Maximum size of a hardware address.
static const size_t MAX_HWADDR_LEN = 20;
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 74f5fe8..1e97205 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -58,11 +58,44 @@ Iface::Iface(const std::string& name, int ifindex)
void
Iface::closeSockets() {
- for (SocketCollection::iterator sock = sockets_.begin();
- sock != sockets_.end(); ++sock) {
- close(sock->sockfd_);
+ // Close IPv4 sockets.
+ closeSockets(AF_INET);
+ // Close IPv6 sockets.
+ closeSockets(AF_INET6);
+}
+
+void
+Iface::closeSockets(const uint16_t family) {
+ // Check that the correect 'family' value has been specified.
+ // The possible values are AF_INET or AF_INET6. Note that, in
+ // the current code they are used to differentiate that the
+ // socket is used to transmit IPv4 or IPv6 traffic. However,
+ // the actual family types of the sockets may be different,
+ // e.g. for LPF we are using raw sockets of AF_PACKET family.
+ //
+ // @todo Consider replacing the AF_INET and AF_INET6 with some
+ // enum which will not be confused with the actual socket type.
+ if ((family != AF_INET) && (family != AF_INET6)) {
+ isc_throw(BadValue, "Invalid socket family " << family
+ << " specified when requested to close all sockets"
+ << " which belong to this family");
+ }
+ // Search for the socket of the specific type.
+ SocketCollection::iterator sock = sockets_.begin();
+ while (sock != sockets_.end()) {
+ if (sock->family_ == family) {
+ // Close and delete the socket and move to the
+ // next one.
+ close(sock->sockfd_);
+ sockets_.erase(sock++);
+
+ } else {
+ // Different type of socket. Let's move
+ // to the next one.
+ ++sock;
+
+ }
}
- sockets_.clear();
}
std::string
@@ -150,6 +183,14 @@ void IfaceMgr::closeSockets() {
}
}
+void
+IfaceMgr::closeSockets(const uint16_t family) {
+ for (IfaceCollection::iterator iface = ifaces_.begin();
+ iface != ifaces_.end(); ++iface) {
+ iface->closeSockets(family);
+ }
+}
+
IfaceMgr::~IfaceMgr() {
// control_buf_ is deleted automatically (scoped_ptr)
control_buf_len_ = 0;
@@ -157,6 +198,42 @@ IfaceMgr::~IfaceMgr() {
closeSockets();
}
+bool
+IfaceMgr::isDirectResponseSupported() const {
+ return (packet_filter_->isDirectResponseSupported());
+}
+
+void
+IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
+ // Do not allow NULL pointer.
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
+ }
+ // Different packet filters use different socket types. It does not make
+ // sense to allow the change of packet filter when there are IPv4 sockets
+ // open because they can't be used by the receive/send functions of the
+ // new packet filter. Below, we check that there are no open IPv4 sockets.
+ // If we find at least one, we have to fail. However, caller still has a
+ // chance to replace the packet filter if he closes sockets explicitly.
+ for (IfaceCollection::const_iterator iface = ifaces_.begin();
+ iface != ifaces_.end(); ++iface) {
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ if (sock->family_ == AF_INET) {
+ // There is at least one socket open, so we have to fail.
+ isc_throw(PacketFilterChangeDenied,
+ "it is not allowed to set new packet"
+ << " filter when there are open IPv4 sockets - need"
+ << " to close them first");
+ }
+ }
+ }
+ // Everything is fine, so replace packet filter.
+ packet_filter_ = packet_filter;
+}
+
+
void IfaceMgr::stubDetectIfaces() {
string ifaceName;
const string v4addr("127.0.0.1"), v6addr("::1");
@@ -758,7 +835,7 @@ IfaceMgr::send(const Pkt4Ptr& pkt) {
// Skip checking if packet filter is non-NULL because it has been
// already checked when packet filter was set.
- return (packet_filter_->send(getSocket(*pkt), pkt));
+ return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
}
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 2085b97..35b4dc0 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -39,10 +39,10 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief IfaceMgr exception thrown when invalid packet filter object specified.
-class InvalidPacketFilter : public Exception {
+/// @brief Exception thrown when it is not allowed to set new Packet Filter.
+class PacketFilterChangeDenied : public Exception {
public:
- InvalidPacketFilter(const char* file, size_t line, const char* what) :
+ PacketFilterChangeDenied(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
@@ -88,7 +88,7 @@ struct SocketInfo {
};
-/// @brief represents a single network interface
+/// @brief Represents a single network interface
///
/// Iface structure represents network interface with all useful
/// information, like name, interface index, MAC address and
@@ -96,13 +96,20 @@ struct SocketInfo {
class Iface {
public:
- /// maximum MAC address length (Infiniband uses 20 bytes)
+ /// Maximum MAC address length (Infiniband uses 20 bytes)
static const unsigned int MAX_MAC_LEN = 20;
- /// type that defines list of addresses
+ /// Type that defines list of addresses
typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
- /// type that holds a list of socket informations
+ /// @brief Type that holds a list of socket information.
+ ///
+ /// @warning The type of the container used here must guarantee
+ /// that the iterators do not invalidate when erase() is called.
+ /// This is because, the \ref closeSockets function removes
+ /// elements selectively by calling erase on the element to be
+ /// removed and further iterates through remaining elements.
+ ///
/// @todo: Add SocketCollectionConstIter type
typedef std::list<SocketInfo> SocketCollection;
@@ -117,6 +124,27 @@ public:
/// @brief Closes all open sockets on interface.
void closeSockets();
+ /// @brief Closes all IPv4 or IPv6 sockets.
+ ///
+ /// This function closes sockets of the specific 'type' and closes them.
+ /// The 'type' of the socket indicates whether it is used to send IPv4
+ /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+ /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+ /// to realize that the actual types of sockets may be different than
+ /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+ /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+ /// case when the Direct IPv4 traffic must be supported. In order to support
+ /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+ /// family sockets on Linux.
+ ///
+ /// @todo Replace the AF_INET and AF_INET6 values with an enum
+ /// which will not be confused with the actual socket type.
+ ///
+ /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+ ///
+ /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+ void closeSockets(const uint16_t family);
+
/// @brief Returns full interface name as "ifname/ifindex" string.
///
/// @return string with interface name
@@ -146,7 +174,7 @@ public:
/// @brief Sets flag_*_ fields based on bitmask value returned by OS
///
- /// Note: Implementation of this method is OS-dependent as bits have
+ /// @note Implementation of this method is OS-dependent as bits have
/// different meaning on each OS.
///
/// @param flags bitmask value returned by OS in interface detection
@@ -237,53 +265,53 @@ public:
const SocketCollection& getSockets() const { return sockets_; }
protected:
- /// socket used to sending data
+ /// Socket used to send data.
SocketCollection sockets_;
- /// network interface name
+ /// Network interface name.
std::string name_;
- /// interface index (a value that uniquely indentifies an interface)
+ /// Interface index (a value that uniquely indentifies an interface).
int ifindex_;
- /// list of assigned addresses
+ /// List of assigned addresses.
AddressCollection addrs_;
- /// link-layer address
+ /// Link-layer address.
uint8_t mac_[MAX_MAC_LEN];
- /// length of link-layer address (usually 6)
+ /// Length of link-layer address (usually 6).
size_t mac_len_;
- /// hardware type
+ /// Hardware type.
uint16_t hardware_type_;
public:
/// @todo: Make those fields protected once we start supporting more
/// than just Linux
- /// specifies if selected interface is loopback
+ /// Specifies if selected interface is loopback.
bool flag_loopback_;
- /// specifies if selected interface is up
+ /// Specifies if selected interface is up.
bool flag_up_;
- /// flag specifies if selected interface is running
- /// (e.g. cable plugged in, wifi associated)
+ /// Flag specifies if selected interface is running
+ /// (e.g. cable plugged in, wifi associated).
bool flag_running_;
- /// flag specifies if selected interface is multicast capable
+ /// Flag specifies if selected interface is multicast capable.
bool flag_multicast_;
- /// flag specifies if selected interface is broadcast capable
+ /// Flag specifies if selected interface is broadcast capable.
bool flag_broadcast_;
- /// interface flags (this value is as is returned by OS,
- /// it may mean different things on different OSes)
+ /// Interface flags (this value is as is returned by OS,
+ /// it may mean different things on different OSes).
uint32_t flags_;
};
-/// @brief handles network interfaces, transmission and reception
+/// @brief Handles network interfaces, transmission and reception.
///
/// IfaceMgr is an interface manager class that detects available network
/// interfaces, configured addresses, link-local addresses, and provides
@@ -291,7 +319,7 @@ public:
///
class IfaceMgr : public boost::noncopyable {
public:
- /// defines callback used when commands are received over control session
+ /// Defines callback used when commands are received over control session.
typedef void (*SessionCallback) (void);
/// @brief Packet reception buffer size
@@ -307,7 +335,7 @@ public:
// 2 maps (ifindex-indexed and name-indexed) and
// also hide it (make it public make tests easier for now)
- /// type that holds a list of interfaces
+ /// Type that holds a list of interfaces.
typedef std::list<Iface> IfaceCollection;
/// IfaceMgr is a singleton class. This method returns reference
@@ -324,7 +352,7 @@ public:
/// the client.
///
/// @return true if direct response is supported.
- bool isDirectResponseSupported();
+ bool isDirectResponseSupported() const;
/// @brief Returns interface with specified interface index
///
@@ -380,11 +408,10 @@ public:
/// @return a socket descriptor
uint16_t getSocket(const isc::dhcp::Pkt4& pkt);
- /// debugging method that prints out all available interfaces
+ /// Debugging method that prints out all available interfaces.
///
/// @param out specifies stream to print list of interfaces to
- void
- printIfaces(std::ostream& out = std::cout);
+ void printIfaces(std::ostream& out = std::cout);
/// @brief Sends an IPv6 packet.
///
@@ -542,10 +569,31 @@ public:
const bool use_bcast = true);
/// @brief Closes all open sockets.
- /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
+ /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
void closeSockets();
- /// @brief returns number of detected interfaces
+ /// @brief Closes all IPv4 or IPv6 sockets.
+ ///
+ /// This function closes sockets of the specific 'type' and closes them.
+ /// The 'type' of the socket indicates whether it is used to send IPv4
+ /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+ /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+ /// to realize that the actual types of sockets may be different than
+ /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+ /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+ /// case when the Direct IPv4 traffic must be supported. In order to support
+ /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+ /// family sockets on Linux.
+ ///
+ /// @todo Replace the AF_INET and AF_INET6 values with an enum
+ /// which will not be confused with the actual socket type.
+ ///
+ /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+ ///
+ /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+ void closeSockets(const uint16_t family);
+
+ /// @brief Returns number of detected interfaces.
///
/// @return number of detected interfaces
uint16_t countIfaces() { return ifaces_.size(); }
@@ -567,18 +615,34 @@ public:
/// Packet Filters expose low-level functions handling sockets opening
/// and sending/receiving packets through those sockets. This function
/// sets custom Packet Filter (represented by a class derived from PktFilter)
- /// to be used by IfaceMgr.
+ /// to be used by IfaceMgr. Note that there must be no IPv4 sockets open
+ /// when this function is called. Call closeSockets(AF_INET) to close
+ /// all hanging IPv4 sockets opened by the current packet filter object.
///
/// @param packet_filter new packet filter to be used by IfaceMgr to send/receive
/// packets and open sockets.
///
/// @throw InvalidPacketFilter if provided packet filter object is NULL.
- void setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
- if (!packet_filter) {
- isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
- }
- packet_filter_ = packet_filter;
- }
+ /// @throw PacketFilterChangeDenied if there are open IPv4 sockets
+ void setPacketFilter(const PktFilterPtr& packet_filter);
+
+ /// @brief Set Packet Filter object to handle send/receive packets.
+ ///
+ /// This function sets Packet Filter object to be used by IfaceMgr,
+ /// appropriate for the current OS. Setting the argument to 'true'
+ /// indicates that function should set a packet filter class
+ /// which supports direct responses to clients having no address
+ /// assigned yet. Filters picked by this function will vary, depending
+ /// on the OS being used. There is no guarantee that there is an
+ /// implementation that supports this feature on a particular OS.
+ /// If there isn't, the PktFilterInet object will be set. If the
+ /// argument is set to 'false', PktFilterInet object instance will
+ /// be set as the Packet Filter regrdaless of the OS type.
+ ///
+ /// @param direct_response_desired specifies whether the Packet Filter
+ /// object being set should support direct traffic to the host
+ /// not having address assigned.
+ void setMatchingPacketFilter(const bool direct_response_desired = false);
/// A value of socket descriptor representing "not specified" state.
static const int INVALID_SOCKET = -1;
@@ -660,14 +724,14 @@ protected:
//int recvsock_; // TODO: should be fd_set eventually, but we have only
//int sendsock_; // 2 sockets for now. Will do for until next release
- // we can't use the same socket, as receiving socket
+ // We can't use the same socket, as receiving socket
// is bound to multicast address. And we all know what happens
// to people who try to use multicast as source address.
- /// length of the control_buf_ array
+ /// Length of the control_buf_ array
size_t control_buf_len_;
- /// control-buffer, used in transmission and reception
+ /// Control-buffer, used in transmission and reception.
boost::scoped_array<char> control_buf_;
/// @brief A wrapper for OS-specific operations before sending IPv4 packet
@@ -687,10 +751,10 @@ protected:
/// @return true if successful, false otherwise
bool os_receive4(struct msghdr& m, Pkt4Ptr& pkt);
- /// socket descriptor of the session socket
+ /// Socket descriptor of the session socket.
int session_socket_;
- /// a callback that will be called when data arrives over session_socket_
+ /// A callback that will be called when data arrives over session_socket_.
SessionCallback session_callback_;
private:
@@ -737,7 +801,7 @@ private:
/// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
/// Packet Filter is the one used for unit testing, which doesn't
/// open sockets but rather mimics their behavior (mock object).
- boost::shared_ptr<PktFilter> packet_filter_;
+ PktFilterPtr packet_filter_;
};
}; // namespace isc::dhcp
diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc
index afd97bb..2293877 100644
--- a/src/lib/dhcp/iface_mgr_bsd.cc
+++ b/src/lib/dhcp/iface_mgr_bsd.cc
@@ -17,6 +17,7 @@
#if defined(OS_BSD)
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
using namespace std;
@@ -34,11 +35,6 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
-bool
-IfaceMgr::isDirectResponseSupported() {
- return (false);
-}
-
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
@@ -54,6 +50,14 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
return (true); // pretend that we have everything set up for reception.
}
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+ // @todo Currently we ignore the preference to use direct traffic
+ // because it hasn't been implemented for BSD systems.
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index 71a32d8..f31c353 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -33,6 +33,8 @@
#include <asiolink/io_address.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_lpf.h>
#include <exceptions/exceptions.h>
#include <util/io/sockaddr_util.h>
@@ -494,11 +496,6 @@ void IfaceMgr::detectIfaces() {
nl.release_list(addr_info);
}
-bool
-IfaceMgr::isDirectResponseSupported() {
- return (false);
-}
-
/// @brief sets flag_*_ fields.
///
/// This implementation is OS-specific as bits have different meaning
@@ -515,6 +512,16 @@ void Iface::setFlags(uint32_t flags) {
flag_broadcast_ = flags & IFF_BROADCAST;
}
+void
+IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
+ if (direct_response_desired) {
+ setPacketFilter(PktFilterPtr(new PktFilterLPF()));
+
+ } else {
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ }
+}
void IfaceMgr::os_send4(struct msghdr&, boost::scoped_array<char>&,
size_t, const Pkt4Ptr&) {
diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc
index 1556b70..46c4a97 100644
--- a/src/lib/dhcp/iface_mgr_sun.cc
+++ b/src/lib/dhcp/iface_mgr_sun.cc
@@ -17,6 +17,7 @@
#if defined(OS_SUN)
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
using namespace std;
@@ -34,11 +35,6 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
-bool
-IfaceMgr::isDirectResponseSupported() {
- return (false);
-}
-
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
@@ -54,6 +50,13 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
return (true); // pretend that we have everything set up for reception.
}
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+ // @todo Currently we ignore the preference to use direct traffic
+ // because it hasn't been implemented for Solaris.
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index e06b163..cd6e313 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -55,7 +55,7 @@ Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
OptionBufferConstIter last)
- :universe_(u), type_(type), data_(OptionBuffer(first,last)) {
+ :universe_(u), type_(type), data_(first, last) {
check();
}
@@ -121,7 +121,7 @@ Option::packOptions(isc::util::OutputBuffer& buf) {
void Option::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
- data_ = OptionBuffer(begin, end);
+ setData(begin, end);
}
void
@@ -274,13 +274,6 @@ void Option::setUint32(uint32_t value) {
writeUint32(value, &data_[0]);
}
-void Option::setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last) {
- // We will copy entire option buffer, so we have to resize data_.
- data_.resize(std::distance(first, last));
- std::copy(first, last, data_.begin());
-}
-
bool Option::equal(const OptionPtr& other) const {
return ( (getType() == other->getType()) &&
(getData() == other->getData()) );
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index 28e65a9..9abb4b3 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -282,8 +282,13 @@ public:
///
/// @param first iterator pointing to beginning of buffer to copy.
/// @param last iterator pointing to end of buffer to copy.
- void setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last);
+ ///
+ /// @tparam InputIterator type of the iterator representing the
+ /// limits of the buffer to be assigned to a data_ buffer.
+ template<typename InputIterator>
+ void setData(InputIterator first, InputIterator last) {
+ data_.assign(first, last);
+ }
/// just to force that every option has virtual dtor
virtual ~Option();
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index e807928..12145fc 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -28,30 +28,20 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
}
OptionCustom::OptionCustom(const OptionDefinition& def,
- Universe u,
- const OptionBuffer& data)
+ Universe u,
+ const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
- // 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();
- }
+ createBuffers(getData());
}
OptionCustom::OptionCustom(const OptionDefinition& def,
- Universe u,
- OptionBufferConstIter first,
- OptionBufferConstIter last)
+ Universe u,
+ OptionBufferConstIter first,
+ OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers(data_);
+ createBuffers(getData());
}
void
@@ -517,9 +507,7 @@ OptionCustom::writeString(const std::string& text, const uint32_t index) {
void
OptionCustom::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
- data_ = OptionBuffer(begin, end);
- // Chop the buffer stored in data_ into set of sub buffers.
- createBuffers(data_);
+ initialize(begin, end);
}
uint16_t
@@ -543,15 +531,13 @@ OptionCustom::len() {
return (length);
}
-void OptionCustom::setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last) {
- // We will copy entire option buffer, so we have to resize data_.
- data_.resize(std::distance(first, last));
- std::copy(first, last, data_.begin());
+void OptionCustom::initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last) {
+ setData(first, last);
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers(data_);
+ createBuffers(getData());
}
std::string OptionCustom::toText(int indent) {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index 5766cb4..e0314ae 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -280,8 +280,8 @@ public:
///
/// @param first iterator pointing to beginning of buffer to copy.
/// @param last iterator pointing to end of buffer to copy.
- void setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last);
+ void initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last);
private:
@@ -336,6 +336,11 @@ private:
std::string dataFieldToText(const OptionDataType data_type,
const uint32_t index) const;
+ /// Make this function private as we don't want it to be invoked
+ /// on OptionCustom object. We rather want that initialize to
+ /// be called instead.
+ using Option::setData;
+
/// Option definition used to create an option.
OptionDefinition definition_;
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 7f0d578..9db99f4 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -22,6 +22,7 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
@@ -160,6 +161,9 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
break;
+ case OPT_STRING_TYPE:
+ return (OptionPtr(new OptionString(u, type, begin, end)));
+
default:
if (u == Option::V6) {
if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
@@ -182,7 +186,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
}
}
- return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
+ return (OptionPtr(new OptionCustom(*this, u, begin, end)));
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
diff --git a/src/lib/dhcp/option_string.cc b/src/lib/dhcp/option_string.cc
new file mode 100644
index 0000000..6a8001a
--- /dev/null
+++ b/src/lib/dhcp/option_string.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/option_string.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value)
+ : Option(u, type) {
+ // Try to assign the provided string value. This will throw exception
+ // if the provided value is empty.
+ setValue(value);
+}
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, type) {
+ // Decode the data. This will throw exception if the buffer is
+ // truncated.
+ unpack(begin, end);
+}
+
+std::string
+OptionString::getValue() const {
+ const OptionBuffer& data = getData();
+ return (std::string(data.begin(), data.end()));
+}
+
+void
+OptionString::setValue(const std::string& value) {
+ // Sanity check that the string value is at least one byte long.
+ // This is a requirement for all currently defined options which
+ // carry a string value.
+ if (value.empty()) {
+ isc_throw(isc::OutOfRange, "string value carried by the option '"
+ << getType() << "' must not be empty");
+ }
+
+ setData(value.begin(), value.end());
+}
+
+
+uint16_t
+OptionString::len() {
+ return (getHeaderLen() + getData().size());
+}
+
+void
+OptionString::pack(isc::util::OutputBuffer& buf) {
+ // Pack option header.
+ packHeader(buf);
+ // Pack data.
+ const OptionBuffer& data = getData();
+ buf.writeData(&data[0], data.size());
+
+ // That's it. We don't pack any sub-options here, because this option
+ // must not contain sub-options.
+}
+
+void
+OptionString::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "failed to parse an option '"
+ << getType() << "' holding string value"
+ << " - empty value is not accepted");
+ }
+ setData(begin, end);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_string.h b/src/lib/dhcp/option_string.h
new file mode 100644
index 0000000..3d0aa9a
--- /dev/null
+++ b/src/lib/dhcp/option_string.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_STRING_H
+#define OPTION_STRING_H
+
+#include <dhcp/option.h>
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Class which represents an option carrying a single string value.
+///
+/// This class represents an option carrying a single string value.
+/// Currently this class imposes that the minimal length of the carried
+/// string is 1.
+///
+/// @todo In the future this class may be extended with some more string
+/// content checks and encoding methods if required.
+class OptionString : public Option {
+public:
+
+ /// @brief Constructor, used to create options to be sent.
+ ///
+ /// This constructor creates an instance of option which carries a
+ /// string value specified as constructor's parameter. This constructor
+ /// is most often used to create an instance of an option which will
+ /// be sent in the outgoing packet.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param value a string value to be carried by the option.
+ ///
+ /// @throw isc::OutOfRange if provided string is empty.
+ OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value);
+
+ /// @brief Constructor, used for receiving options.
+ ///
+ /// This constructor creates an instance of the option from the provided
+ /// chunk of buffer. This buffer may hold the data received on the wire.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param begin iterator pointing to the first byte of the buffer chunk.
+ /// @param end iterator pointing to the last byte of the buffer chunk.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns length of the whole option, including header.
+ ///
+ /// @return length of the whole option.
+ virtual uint16_t len();
+
+ /// @brief Returns the string value held by the option.
+ ///
+ /// @return string value held by the option.
+ std::string getValue() const;
+
+ /// @brief Sets the string value to be held by the option.
+ ///
+ /// @param value string value to be set.
+ ///
+ /// @throw isc::OutOfRange if a string value to be set is empty.
+ void setValue(const std::string& value);
+
+ /// @brief Creates on-wire format of the option.
+ ///
+ /// This function creates on-wire format of the option and appends it to
+ /// the data existing in the provided buffer. The internal buffer's pointer
+ /// is moved to the end of stored data.
+ ///
+ /// @param [out] buf output buffer where the option will be stored.
+ virtual void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Decodes option data from the provided buffer.
+ ///
+ /// This function decodes option data from the provided buffer. Note that
+ /// it does not decode the option code and length, so the iterators must
+ /// point to the begining and end of the option payload respectively.
+ /// The size of the decoded payload must be at least 1 byte.
+ ///
+ /// @param begin the iterator pointing to the option payload.
+ /// @param end the iterator pointing to the end of the option payload.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+};
+
+/// Pointer to the OptionString object.
+typedef boost::shared_ptr<OptionString> OptionStringPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_STRING_H
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 0592807..919235b 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -276,8 +276,24 @@ Pkt4::toText() {
}
void
-Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
+Pkt4::setHWAddr(uint8_t htype, uint8_t hlen,
const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, hwaddr_);
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL"
+ << " is forbidden");
+ }
+ hwaddr_ = addr;
+}
+
+void
+Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr) {
/// @todo Rewrite this once support for client-identifier option
/// is implemented (ticket 1228?)
if (hlen > MAX_CHADDR_LEN) {
@@ -288,15 +304,37 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
isc_throw(OutOfRange, "Invalid HW Address specified");
}
- hwaddr_.reset(new HWAddr(mac_addr, hType));
+ hw_addr.reset(new HWAddr(mac_addr, htype));
}
void
-Pkt4::setHWAddr(const HWAddrPtr& addr) {
+Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_);
+}
+
+void
+Pkt4::setLocalHWAddr(const HWAddrPtr& addr) {
if (!addr) {
- isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+ isc_throw(BadValue, "Setting local HW address to NULL is"
+ << " forbidden.");
}
- hwaddr_ = addr;
+ local_hwaddr_ = addr;
+}
+
+void
+Pkt4::setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, remote_hwaddr_);
+}
+
+void
+Pkt4::setRemoteHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting remote HW address to NULL is"
+ << " forbidden.");
+ }
+ remote_hwaddr_ = addr;
}
void
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index e7f33c5..d232745 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -47,6 +47,10 @@ public:
/// specifies DHCPv4 packet header length (fixed part)
const static size_t DHCPV4_PKT_HDR_LEN = 236;
+ /// Mask for the value of flags field in the DHCPv4 message
+ /// to check whether client requested broadcast response.
+ const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
+
/// Constructor, used in replying to a message.
///
/// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -267,10 +271,10 @@ public:
///
/// Note: mac_addr must be a buffer of at least hlen bytes.
///
- /// @param hType hardware type (will be sent in htype field)
+ /// @param htype hardware type (will be sent in htype field)
/// @param hlen hardware length (will be sent in hlen field)
/// @param mac_addr pointer to hardware address
- void setHWAddr(uint8_t hType, uint8_t hlen,
+ void setHWAddr(uint8_t htype, uint8_t hlen,
const std::vector<uint8_t>& mac_addr);
/// @brief Sets hardware address
@@ -363,6 +367,72 @@ public:
/// @return interface index
uint32_t getIndex() const { return (ifindex_); };
+ /// @brief Sets remote HW address.
+ ///
+ /// Sets the destination HW address for the outgoing packet
+ /// or source HW address for the incoming packet. When this
+ /// is an outgoing packet this address will be used to construct
+ /// the link layer header.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets remote HW address.
+ ///
+ /// Sets hardware address from an existing HWAddr structure.
+ /// The remote address is a destination address for outgoing
+ /// packet and source address for incoming packet. When this
+ /// is an outgoing packet, this address will be used to
+ /// construct the link layer header.
+ ///
+ /// @param addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setRemoteHWAddr(const HWAddrPtr& addr);
+
+ /// @brief Returns the remote HW address.
+ ///
+ /// @return remote HW address.
+ HWAddrPtr getRemoteHWAddr() const {
+ return (remote_hwaddr_);
+ }
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets the source HW address for the outgoing packet or
+ /// destination HW address for the incoming packet.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets hardware address from an existing HWAddr structure.
+ /// The local address is a source address for outgoing
+ /// packet and destination address for incoming packet.
+ ///
+ /// @param addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setLocalHWAddr(const HWAddrPtr& addr);
+
+ /// @brief Returns local HW address.
+ ///
+ /// @return local HW addr.
+ HWAddrPtr getLocalHWAddr() const {
+ return (local_hwaddr_);
+ }
+
/// @brief Sets remote address.
///
/// @param remote specifies remote address
@@ -419,6 +489,23 @@ public:
/// @throw isc::Unexpected if timestamp update failed
void updateTimestamp();
+private:
+
+ /// @brief Generic method that validates and sets HW address.
+ ///
+ /// This is a generic method used by all modifiers of this class
+ /// which set class members representing HW address.
+ ///
+ /// @param htype hardware type.
+ /// @param hlen hardware length.
+ /// @param mac_addr pointer to actual hardware address.
+ /// @param [out] hw_addr pointer to a class member to be modified.
+ ///
+ /// @trow isc::OutOfRange if invalid HW address specified.
+ void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr);
+
protected:
/// converts DHCP message type to BOOTP op type
@@ -429,6 +516,12 @@ protected:
uint8_t
DHCPTypeToBootpType(uint8_t dhcpType);
+ /// local HW address (dst if receiving packet, src if sending packet)
+ HWAddrPtr local_hwaddr_;
+
+ // remote HW address (src if receiving packet, dst if sending packet)
+ HWAddrPtr remote_hwaddr_;
+
/// local address (dst if receiving packet, src if sending packet)
isc::asiolink::IOAddress local_addr_;
@@ -533,6 +626,7 @@ protected:
/// packet timestamp
boost::posix_time::ptime timestamp_;
+
}; // Pkt4 class
typedef boost::shared_ptr<Pkt4> Pkt4Ptr;
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
index 946bd14..204b25e 100644
--- a/src/lib/dhcp/pkt_filter.h
+++ b/src/lib/dhcp/pkt_filter.h
@@ -15,11 +15,21 @@
#ifndef PKT_FILTER_H
#define PKT_FILTER_H
+#include <dhcp/pkt4.h>
#include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
+/// @brief Exception thrown when invalid packet filter object specified.
+class InvalidPacketFilter : public Exception {
+public:
+ InvalidPacketFilter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Forward declaration to the structure describing a socket.
struct SocketInfo;
/// Forward declaration to the class representing interface
@@ -45,6 +55,18 @@ public:
/// @brief Virtual Destructor
virtual ~PktFilter() { }
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// Checks if the Packet Filter class has capability to send a packet
+ /// directly to the client having no address assigned. This capability
+ /// is used by DHCPv4 servers which respond to the clients they assign
+ /// addresses to. Not all classes derived from PktFilter support this
+ /// because it requires injection of the destination host HW address to
+ /// the link layer header of the packet.
+ ///
+ /// @return true of the direct response is supported.
+ virtual bool isDirectResponseSupported() const = 0;
+
/// @brief Open socket.
///
/// @param iface interface descriptor
@@ -71,13 +93,18 @@ public:
/// @brief Send packet over specified socket.
///
+ /// @param iface interface to be used to send packet
/// @param sockfd socket descriptor
/// @param pkt packet to be sent
///
/// @return result of sending the packet. It is 0 if successful.
- virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt) = 0;
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt) = 0;
};
+/// Pointer to a PktFilter object.
+typedef boost::shared_ptr<PktFilter> PktFilterPtr;
+
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc
index a6360aa..62695e5 100644
--- a/src/lib/dhcp/pkt_filter_inet.cc
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -28,25 +28,12 @@ PktFilterInet::PktFilterInet()
{
}
-// iface is only used when SO_BINDTODEVICE is defined and thus
-// the code section using this variable is compiled.
-#ifdef SO_BINDTODEVICE
int PktFilterInet::openSocket(const Iface& iface,
const isc::asiolink::IOAddress& addr,
const uint16_t port,
const bool receive_bcast,
const bool send_bcast) {
-#else
-int PktFilterInet::openSocket(const Iface&,
- const isc::asiolink::IOAddress& addr,
- const uint16_t port,
- const bool receive_bcast,
- const bool send_bcast) {
-
-
-#endif
-
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(sockaddr));
addr4.sin_family = AF_INET;
@@ -54,7 +41,7 @@ int PktFilterInet::openSocket(const Iface&,
// If we are to receive broadcast messages we have to bind
// to "ANY" address.
- if (receive_bcast) {
+ if (receive_bcast && iface.flag_broadcast_) {
addr4.sin_addr.s_addr = INADDR_ANY;
} else {
addr4.sin_addr.s_addr = htonl(addr);
@@ -66,7 +53,7 @@ int PktFilterInet::openSocket(const Iface&,
}
#ifdef SO_BINDTODEVICE
- if (receive_bcast) {
+ if (receive_bcast && iface.flag_broadcast_) {
// Bind to device so as we receive traffic on a specific interface.
if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
iface.getName().length() + 1) < 0) {
@@ -77,7 +64,7 @@ int PktFilterInet::openSocket(const Iface&,
}
#endif
- if (send_bcast) {
+ if (send_bcast && iface.flag_broadcast_) {
// Enable sending to broadcast address.
int flag = 1;
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
@@ -198,7 +185,8 @@ PktFilterInet::receive(const Iface& iface, const SocketInfo& socket_info) {
}
int
-PktFilterInet::send(uint16_t sockfd, const Pkt4Ptr& pkt) {
+PktFilterInet::send(const Iface&, uint16_t sockfd,
+ const Pkt4Ptr& pkt) {
memset(&control_buf_[0], 0, control_buf_len_);
// Set the target address we're sending to.
diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h
index 4e98612..95c9224 100644
--- a/src/lib/dhcp/pkt_filter_inet.h
+++ b/src/lib/dhcp/pkt_filter_inet.h
@@ -16,6 +16,7 @@
#define PKT_FILTER_INET_H
#include <dhcp/pkt_filter.h>
+#include <boost/scoped_array.hpp>
namespace isc {
namespace dhcp {
@@ -32,6 +33,17 @@ public:
/// Allocates control buffer.
PktFilterInet();
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This Packet Filter sends packets through AF_INET datagram sockets, so
+ /// it can't inject the HW address of the destionation host into the packet.
+ /// Therefore this class does not support direct responses.
+ ///
+ /// @return false always.
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
/// @brief Open socket.
///
/// @param iface interface descriptor
@@ -57,11 +69,13 @@ public:
/// @brief Send packet over specified socket.
///
+ /// @param iface interface to be used to send packet
/// @param sockfd socket descriptor
/// @param pkt packet to be sent
///
/// @return result of sending a packet. It is 0 if successful.
- virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
private:
/// Length of the control_buf_ array.
diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc
index ef75426..e0964d5 100644
--- a/src/lib/dhcp/pkt_filter_lpf.cc
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -13,31 +13,254 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
+#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <exceptions/exceptions.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+
+namespace {
+
+using namespace isc::dhcp;
+
+/// The following structure defines a Berkely Packet Filter program to perform
+/// packet filtering. The program operates on Ethernet packets. To help with
+/// interpretation of the program, for the types of Ethernet packets we are
+/// interested in, the header layout is:
+///
+/// 6 bytes Destination Ethernet Address
+/// 6 bytes Source Ethernet Address
+/// 2 bytes Ethernet packet type
+///
+/// 20 bytes Fixed part of IP header
+/// variable Variable part of IP header
+///
+/// 2 bytes UDP Source port
+/// 2 bytes UDP destination port
+/// 4 bytes Rest of UDP header
+///
+/// @todo We may want to extend the filter to receive packets sent
+/// to the particular IP address assigned to the interface or
+/// broadcast address.
+struct sock_filter dhcp_sock_filter [] = {
+ // Make sure this is an IP packet: check the half-word (two bytes)
+ // at offset 12 in the packet (the Ethernet packet type). If it
+ // is, advance to the next instruction. If not, advance 8
+ // instructions (which takes execution to the last instruction in
+ // the sequence: "drop it").
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+ // Make sure it's a UDP packet. The IP protocol is at offset
+ // 9 in the IP header so, adding the Ethernet packet header size
+ // of 14 bytes gives an absolute byte offset in the packet of 23.
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+ // Make sure this isn't a fragment by checking that the fragment
+ // offset field in the IP header is zero. This field is the
+ // least-significant 13 bits in the bytes at offsets 6 and 7 in
+ // the IP header, so the half-word at offset 20 (6 + size of
+ // Ethernet header) is loaded and an appropriate mask applied.
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ // Get the IP header length. This is achieved by the following
+ // (special) instruction that, given the offset of the start
+ // of the IP header (offset 14) loads the IP header length.
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
+
+ // Make sure it's to the right port. The following instruction
+ // adds the previously extracted IP header length to the given
+ // offset to locate the correct byte. The given offset of 16
+ // comprises the length of the Ethernet header (14) plus the offset
+ // of the UDP destination port (2) within the UDP header.
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
+ // The following instruction tests against the default DHCP server port,
+ // but the action port is actually set in PktFilterLPF::openSocket().
+ // N.B. The code in that method assumes that this instruction is at
+ // offset 8 in the program. If this is changed, openSocket() must be
+ // updated.
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+ // If we passed all the tests, ask for the whole packet.
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ // Otherwise, drop it.
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+}
+
+using namespace isc::util;
namespace isc {
namespace dhcp {
int
-PktFilterLPF::openSocket(const Iface&, const isc::asiolink::IOAddress&,
- const uint16_t, const bool,
+PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
+ const uint16_t port, const bool,
const bool) {
- isc_throw(isc::NotImplemented,
- "Linux Packet Filtering is not implemented yet");
+
+ int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create raw LPF socket");
+ }
+
+ // Create socket filter program. This program will only allow incoming UDP
+ // traffic which arrives on the specific (DHCP) port). It will also filter
+ // out all fragmented packets.
+ struct sock_fprog filter_program;
+ memset(&filter_program, 0, sizeof(filter_program));
+
+ filter_program.filter = dhcp_sock_filter;
+ filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter);
+ // Override the default port value.
+ dhcp_sock_filter[8].k = port;
+ // Apply the filter.
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
+ sizeof(filter_program)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to install packet filtering program"
+ << " on the socket " << sock);
+ }
+
+ struct sockaddr_ll sa;
+ memset(&sa, 0, sizeof(sockaddr_ll));
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+
+ // For raw sockets we construct IP headers on our own, so we don't bind
+ // socket to IP address but to the interface. We will later use the
+ // Linux Packet Filtering to filter out these packets that we are
+ // interested in.
+ if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sa)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
+ << "' to interface '" << iface.getName() << "'");
+ }
+
+ return (sock);
+
}
Pkt4Ptr
-PktFilterLPF::receive(const Iface&, const SocketInfo&) {
- isc_throw(isc::NotImplemented,
- "Linux Packet Filtering is not implemented yet");
+PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
+ uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+ int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
+ // If negative value is returned by read(), it indicates that an
+ // error occured. If returned value is 0, no data was read from the
+ // socket. In both cases something has gone wrong, because we expect
+ // that a chunk of data is there. We signal the lack of data by
+ // returing an empty packet.
+ if (data_len <= 0) {
+ return Pkt4Ptr();
+ }
+
+ InputBuffer buf(raw_buf, data_len);
+
+ // @todo: This is awkward way to solve the chicken and egg problem
+ // whereby we don't know the offset where DHCP data start in the
+ // received buffer when we create the packet object. In general case,
+ // the IP header has variable length. The information about its length
+ // is stored in one of its fields. Therefore, we have to decode the
+ // packet to get the offset of the DHCP data. The dummy object is
+ // created so as we can pass it to the functions which decode IP stack
+ // and find actual offset of the DHCP data.
+ // Once we find the offset we can create another Pkt4 object from
+ // the reminder of the input buffer and set the IP addresses and
+ // ports from the dummy packet. We should consider doing it
+ // in some more elegant way.
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Read the DHCP data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+ // Decode DHCP data into the Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+ // Set the appropriate packet members using data collected from
+ // the decoded headers.
+ pkt->setIndex(iface.getIndex());
+ pkt->setIface(iface.getName());
+ pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+ pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+ pkt->setLocalPort(dummy_pkt->getLocalPort());
+ pkt->setRemotePort(dummy_pkt->getRemotePort());
+ pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+ pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+ return (pkt);
}
int
-PktFilterLPF::send(uint16_t, const Pkt4Ptr&) {
- isc_throw(isc::NotImplemented,
- "Linux Packet Filtering is not implemented yet");
+PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+
+ OutputBuffer buf(14);
+
+ HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+ iface.getHWType()));
+ pkt->setLocalHWAddr(hwaddr);
+
+
+ // Ethernet frame header.
+ // Note that we don't validate whether HW addresses in 'pkt'
+ // are valid because they are checked by the function called.
+ writeEthernetHeader(pkt, buf);
+
+ // This object represents broadcast address. We will compare the
+ // local packet address with it a few lines below. Having static
+ // variable guarantees that this object is created only once, not
+ // every time this function is called.
+ static const isc::asiolink::IOAddress bcast_addr("255.255.255.255");
+
+ // It is likely that the local address in pkt object is set to
+ // broadcast address. This is the case if server received the
+ // client's packet on broadcast address. Therefore, we need to
+ // correct it here and assign the actual source address.
+ if (pkt->getLocalAddr() == bcast_addr) {
+ const Iface::SocketCollection& sockets = iface.getSockets();
+ for (Iface::SocketCollection::const_iterator it = sockets.begin();
+ it != sockets.end(); ++it) {
+ if (sockfd == it->sockfd_) {
+ pkt->setLocalAddr(it->addr_);
+ }
+ }
+ }
+
+ // IP and UDP header
+ writeIpUdpHeader(pkt, buf);
+
+ // DHCPv4 message
+ buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+
+ sockaddr_ll sa;
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+ sa.sll_protocol = htons(ETH_P_IP);
+ sa.sll_halen = 6;
+
+ int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
+ reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sockaddr_ll));
+ if (result < 0) {
+ isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno="
+ << errno << " (check errno.h)");
+ }
+
+ return (0);
+
}
diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h
index 67b190f..d36719f 100644
--- a/src/lib/dhcp/pkt_filter_lpf.h
+++ b/src/lib/dhcp/pkt_filter_lpf.h
@@ -17,6 +17,8 @@
#include <dhcp/pkt_filter.h>
+#include <util/buffer.h>
+
namespace isc {
namespace dhcp {
@@ -30,6 +32,15 @@ namespace dhcp {
class PktFilterLPF : public PktFilter {
public:
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This class supports direct responses to the host without address.
+ ///
+ /// @return true always.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
/// @brief Open socket.
///
/// @param iface interface descriptor
@@ -57,12 +68,14 @@ public:
/// @brief Send packet over specified socket.
///
+ /// @param iface interface to be used to send packet
/// @param sockfd socket descriptor
/// @param pkt packet to be sent
///
/// @throw isc::NotImplemented always
/// @return result of sending a packet. It is 0 if successful.
- virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt);
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
};
diff --git a/src/lib/dhcp/protocol_util.cc b/src/lib/dhcp/protocol_util.cc
new file mode 100644
index 0000000..d93f8c4
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.cc
@@ -0,0 +1,243 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/protocol_util.h>
+#include <boost/static_assert.hpp>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+decodeEthernetHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer to be parsed must not be lower
+ // then the size of the Ethernet frame header.
+ if (buf.getLength() - buf.getPosition() < ETHERNET_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "size of ethernet header in received "
+ << "packet is invalid, expected at least "
+ << ETHERNET_HEADER_LEN << " bytes, received "
+ << buf.getLength() - buf.getPosition() << " bytes");
+ }
+ // Packet object must not be NULL. We want to output some values
+ // to this object.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing ethernet"
+ " frame header");
+ }
+
+ // The size of the single address is always lower then the size of
+ // the header that holds this address. Otherwise, it is a programming
+ // error that we want to detect in the compilation time.
+ BOOST_STATIC_ASSERT(ETHERNET_HEADER_LEN > HWAddr::ETHERNET_HWADDR_LEN);
+
+ // Remember initial position.
+ size_t start_pos = buf.getPosition();
+
+ // Read the destination HW address.
+ std::vector<uint8_t> dest_addr;
+ buf.readVector(dest_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setLocalHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, dest_addr);
+ // Read the source HW address.
+ std::vector<uint8_t> src_addr;
+ buf.readVector(src_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setRemoteHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, src_addr);
+ // Move the buffer read pointer to the end of the Ethernet frame header.
+ buf.setPosition(start_pos + ETHERNET_HEADER_LEN);
+}
+
+void
+decodeIpUdpHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer must be at least equal to the minimal size of
+ // the IPv4 packet header plus UDP header length.
+ if (buf.getLength() - buf.getPosition() < MIN_IP_HEADER_LEN + UDP_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "the total size of the IP and UDP headers in "
+ << "received packet is invalid, expected at least "
+ << MIN_IP_HEADER_LEN + UDP_HEADER_LEN
+ << " bytes, received " << buf.getLength() - buf.getPosition()
+ << " bytes");
+ }
+
+ // Packet object must not be NULL.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing IP and UDP"
+ " packet headers");
+ }
+
+ BOOST_STATIC_ASSERT(IP_SRC_ADDR_OFFSET < MIN_IP_HEADER_LEN);
+
+ // Remember initial position of the read pointer.
+ size_t start_pos = buf.getPosition();
+
+ // Read IP header length (mask most significant bits as they indicate IP version).
+ uint8_t ip_len = buf.readUint8() & 0xF;
+ // IP length is the number of 4 byte chunks that construct IPv4 header.
+ // It must not be lower than 5 because first 20 bytes are fixed.
+ if (ip_len < 5) {
+ isc_throw(InvalidPacketHeader, "Value of the length of the IP header must not be"
+ << " lower than 5 words. The length of the received header is "
+ << ip_len << ".");
+ }
+
+ // Seek to the position of source IP address.
+ buf.setPosition(start_pos + IP_SRC_ADDR_OFFSET);
+ // Read source address.
+ pkt->setRemoteAddr(IOAddress(buf.readUint32()));
+ // Read destination address.
+ pkt->setLocalAddr(IOAddress(buf.readUint32()));
+
+ // Skip IP header options (if any) to start of the
+ // UDP header.
+ buf.setPosition(start_pos + ip_len * 4);
+
+ // Read source port from UDP header.
+ pkt->setRemotePort(buf.readUint16());
+ // Read destination port from UDP header.
+ pkt->setLocalPort(buf.readUint16());
+
+ // Set the pointer position to the first byte o the
+ // UDP payload (DHCP packet).
+ buf.setPosition(start_pos + ip_len * 4 + UDP_HEADER_LEN);
+}
+
+void
+writeEthernetHeader(const Pkt4Ptr& pkt, OutputBuffer& out_buf) {
+ // Set destination HW address.
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ if (remote_addr) {
+ if (remote_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+ out_buf.writeData(&remote_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ isc_throw(BadValue, "invalid size of the remote HW address "
+ << remote_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // HW address has not been specified. This is possible when receiving
+ // packet through a logical interface (e.g. lo). In such cases, we
+ // don't want to fail but rather provide a default HW address, which
+ // consists of zeros.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Set source HW address.
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ if (local_addr) {
+ if (local_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+ out_buf.writeData(&local_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ isc_throw(BadValue, "invalid size of the local HW address "
+ << local_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // Provide default HW address.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Type IP.
+ out_buf.writeUint16(ETHERNET_TYPE_IP);
+}
+
+void
+writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) {
+
+ out_buf.writeUint8(0x45); // IP version 4, IP header length 5
+ out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN
+ out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length.
+ out_buf.writeUint16(0); // Identification
+ out_buf.writeUint16(0x4000); // Disable fragmentation.
+ out_buf.writeUint8(128); // TTL
+ out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP.
+ out_buf.writeUint16(0); // Temporarily set checksum to 0.
+ out_buf.writeUint32(pkt->getLocalAddr()); // Source address.
+ out_buf.writeUint32(pkt->getRemoteAddr()); // Destination address.
+
+ // Calculate pseudo header checksum. It will be necessary to compute
+ // UDP checksum.
+ // Get the UDP length. This includes udp header's and data length.
+ uint32_t udp_len = 8 + pkt->getBuffer().getLength();
+ // The magic number "8" indicates the offset where the source address
+ // is stored in the buffer. This offset is counted here from the
+ // current tail of the buffer. Starting from this offset we calculate
+ // the checksum using 8 following bytes of data. This will include
+ // 4 bytes of source address and 4 bytes of destination address.
+ // The IPPROTO_UDP and udp_len are also added up to the checksum.
+ uint16_t pseudo_hdr_checksum =
+ calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8,
+ 8, IPPROTO_UDP + udp_len);
+
+ // Calculate IP header checksum.
+ uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData())
+ + out_buf.getLength() - 20, 20);
+ // Write checksum in the IP header. The offset of the checksum is 10 bytes
+ // back from the tail of the current buffer.
+ out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10);
+
+ // Start UDP header.
+ out_buf.writeUint16(pkt->getLocalPort()); // Source port.
+ out_buf.writeUint16(pkt->getRemotePort()); // Destination port.
+ out_buf.writeUint16(udp_len); // Length of the header and data.
+
+ // Checksum is calculated from the contents of UDP header, data and pseudo ip header.
+ // The magic number "6" indicates that the UDP header starts at offset 6 from the
+ // tail of the current buffer. These 6 bytes contain source and destination port
+ // as well as the length of the header.
+ uint16_t udp_checksum =
+ ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6,
+ calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()),
+ pkt->getBuffer().getLength(),
+ pseudo_hdr_checksum));
+ // Write UDP checksum.
+ out_buf.writeUint16(udp_checksum);
+}
+
+uint16_t
+calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) {
+ uint32_t i;
+ for (i = 0; i < (buf_size & ~1U); i += 2) {
+ uint16_t chunk = buf[i] << 8 | buf[i + 1];
+ sum += chunk;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+ // If one byte has left, we also need to add it to the checksum.
+ if (i < buf_size) {
+ sum += buf[i] << 8;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+
+ return (sum);
+
+}
+
+}
+}
diff --git a/src/lib/dhcp/protocol_util.h b/src/lib/dhcp/protocol_util.h
new file mode 100644
index 0000000..b3f8085
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.h
@@ -0,0 +1,153 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PROTOCOL_UTIL_H
+#define PROTOCOL_UTIL_H
+
+#include <dhcp/pkt4.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when error occured during parsing packet's headers.
+///
+/// This exception is thrown when parsing link, Internet or Transport layer
+/// header has failed.
+class InvalidPacketHeader : public Exception {
+public:
+ InvalidPacketHeader(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Size of the Ethernet frame header.
+static const size_t ETHERNET_HEADER_LEN = 14;
+/// Offset of the 2-byte word in the Ethernet packet which
+/// holds the type of the protocol it encapsulates.
+static const size_t ETHERNET_PACKET_TYPE_OFFSET = 12;
+/// This value is held in the Ethertype field of Ethernet frame
+/// and indicates that an IP packet is encapsulated with this
+/// frame. In the standard headers, there is an ETHERTYPE_IP,
+/// constant which serves the same purpose. However, it is more
+/// convenient to have our constant because we avoid
+/// inclusion of additional headers, which have different names
+/// and locations on different OSes.
+static const uint16_t ETHERNET_TYPE_IP = 0x0800;
+
+/// Minimal IPv4 header length.
+static const size_t MIN_IP_HEADER_LEN = 20;
+/// Offset in the IP header where the flags field starts.
+static const size_t IP_FLAGS_OFFSET = 6;
+/// Offset of the byte in IP header which holds the type
+/// of the protocol it encapsulates.
+static const size_t IP_PROTO_TYPE_OFFSET = 9;
+/// Offset of source address in the IPv4 header.
+static const size_t IP_SRC_ADDR_OFFSET = 12;
+
+/// UDP header length.
+static const size_t UDP_HEADER_LEN = 8;
+/// Offset within UDP header where destination port is held.
+static const size_t UDP_DEST_PORT = 2;
+
+/// @brief Decode the Ethernet header.
+///
+/// This function reads Ethernet frame header from the provided
+/// buffer at the current read position. The source HW address
+/// is read from the header and assigned as client address in
+/// the pkt object. The buffer read pointer is set to the end
+/// of the Ethernet frame header if read was successful.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding header to be parsed.
+/// @param [out] pkt packet object receiving HW source address read from header.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeEthernetHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Decode IP and UDP header.
+///
+/// This function reads IP and UDP headers from the provided buffer
+/// at the current read position. The source and destination IP
+/// addresses and ports and read from these headers and stored in
+/// the appropriate members of the pkt object.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding headers to be parsed.
+/// @param [out] pkt packet object where IP addresses and ports
+/// are stored.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeIpUdpHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Writes ethernet frame header into a buffer.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt packet object holding source and destination HW address.
+/// @param [out] out_buf buffer where a header is written.
+void writeEthernetHeader(const Pkt4Ptr& pkt,
+ util::OutputBuffer& out_buf);
+
+/// @brief Writes both IP and UDP header into output buffer
+///
+/// This utility function assembles IP and UDP packet headers for the
+/// provided DHCPv4 message. The source and destination addreses and
+/// ports stored in the pkt object are copied as source and destination
+/// addresses and ports into IP/UDP headers.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt DHCPv4 packet to be sent in IP packet
+/// @param [out] out_buf buffer where an IP header is written
+void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf);
+
+/// @brief Calculates checksum for provided buffer
+///
+/// This function returns the sum of 16-bit values from the provided
+/// buffer. If the third parameter is specified, it indicates the
+/// initial checksum value. This parameter can be a result of
+/// calcChecksum function's invocation on different data buffer.
+/// The IP or UDP checksum value is a complement of the result returned
+/// by this function. However, this function does not compute complement
+/// of the summed values. It must be calculated outside of this function
+/// before writing the value to the packet buffer.
+///
+/// The IP header checksum calculation algorithm has been defined in
+/// <a href="https://tools.ietf.org/html/rfc791#page-14">RFC 791</a>
+///
+/// @param buf buffer for which the checksum is calculated.
+/// @param buf_size size of the buffer for which checksum is calculated.
+/// @param sum initial checksum value, other values will be added to it.
+///
+/// @return calculated checksum.
+uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size,
+ uint32_t sum = 0);
+
+}
+}
+#endif // PROTOCOL_UTIL_H
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 839a5d9..9bd19a1 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -84,7 +84,7 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
{ "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
{ "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
- { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "domain-name", DHO_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "extensions-path", DHO_EXTENSIONS_PATH, OPT_STRING_TYPE,
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index c868553..0216a0b 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -41,8 +41,16 @@ libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
libdhcp___unittests_SOURCES += option_space_unittest.cc
+libdhcp___unittests_SOURCES += option_string_unittest.cc
libdhcp___unittests_SOURCES += pkt4_unittest.cc
libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+
+if OS_LINUX
+libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
+endif
+
+libdhcp___unittests_SOURCES += protocol_util_unittest.cc
libdhcp___unittests_SOURCES += duid_unittest.cc
libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index ede7abf..800f5e1 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -74,6 +74,10 @@ public:
: open_socket_called_(false) {
}
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
/// Pretends to open socket. Only records a call to this function.
virtual int openSocket(const Iface&,
const isc::asiolink::IOAddress&,
@@ -91,7 +95,7 @@ public:
}
/// Does nothing
- virtual int send(uint16_t, const Pkt4Ptr&) {
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
return (0);
}
@@ -103,7 +107,8 @@ public:
class NakedIfaceMgr: public IfaceMgr {
// "Naked" Interface Manager, exposes internal fields
public:
- NakedIfaceMgr() { }
+ NakedIfaceMgr() {
+ }
IfaceCollection & getIfacesLst() { return ifaces_; }
};
@@ -117,6 +122,24 @@ public:
~IfaceMgrTest() {
}
+ // Get ther number of IPv4 or IPv6 sockets on the loopback interface
+ int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
+ // Get all sockets.
+ Iface::SocketCollection sockets = iface.getSockets();
+
+ // Loop through sockets and try to find the ones which match the
+ // specified type.
+ int sockets_count = 0;
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ // Match found, increase the counter.
+ if (sock->family_ == family) {
+ ++sockets_count;
+ }
+ }
+ return (sockets_count);
+ }
+
};
// We need some known interface to work reliably. Loopback interface is named
@@ -208,6 +231,66 @@ TEST_F(IfaceMgrTest, basic) {
ASSERT_TRUE(&ifacemgr != 0);
}
+
+// This test verifies that sockets can be closed selectively, i.e. all
+// IPv4 sockets can be closed first and all IPv6 sockets remain open.
+TEST_F(IfaceMgrTest, closeSockets) {
+ // Will be using local loopback addresses for this test.
+ IOAddress loaddr("127.0.0.1");
+ IOAddress loaddr6("::1");
+
+ // Create instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Out constructor does not detect interfaces by itself. We need
+ // to create one and add.
+ int ifindex = if_nametoindex(LOOPBACK);
+ ASSERT_GT(ifindex, 0);
+ Iface lo_iface(LOOPBACK, ifindex);
+ iface_mgr->getIfacesLst().push_back(lo_iface);
+
+ // Create set of V4 and V6 sockets on the loopback interface.
+ // They must differ by a port they are bound to.
+ for (int i = 0; i < 6; ++i) {
+ // Every other socket will be IPv4.
+ if (i % 2) {
+ ASSERT_NO_THROW(
+ iface_mgr->openSocket(LOOPBACK, loaddr, 10000 + i)
+ );
+ } else {
+ ASSERT_NO_THROW(
+ iface_mgr->openSocket(LOOPBACK, loaddr6, 10000 + i)
+ );
+ }
+ }
+
+ // At the end we should have 3 IPv4 and 3 IPv6 sockets open.
+ Iface* iface = iface_mgr->getIface(LOOPBACK);
+ ASSERT_TRUE(iface != NULL);
+
+ int v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ ASSERT_EQ(3, v4_sockets_count);
+ int v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ ASSERT_EQ(3, v6_sockets_count);
+
+ // Let's try to close only IPv4 sockets.
+ ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET));
+ v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ EXPECT_EQ(0, v4_sockets_count);
+ // The IPv6 sockets should remain open.
+ v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ EXPECT_EQ(3, v6_sockets_count);
+
+ // Let's try to close IPv6 sockets.
+ ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET6));
+ v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ EXPECT_EQ(0, v4_sockets_count);
+ // They should have been closed now.
+ v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ EXPECT_EQ(0, v6_sockets_count);
+}
+
TEST_F(IfaceMgrTest, ifaceClass) {
// Basic tests for Iface inner class
@@ -277,8 +360,8 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547)
);
- // Socket is open if its descriptor is greater than zero.
- ASSERT_GT(socket1, 0);
+ // Socket is open if result is non-negative.
+ ASSERT_GE(socket1, 0);
// Remember when we call receive6().
ptime start_time = microsec_clock::universal_time();
@@ -329,8 +412,8 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10067)
);
- // Socket is open if its descriptor is greater than zero.
- ASSERT_GT(socket1, 0);
+ // Socket is open if returned value is non-negative.
+ ASSERT_GE(socket1, 0);
Pkt4Ptr pkt;
// Remember when we call receive4().
@@ -379,7 +462,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
);
- ASSERT_GT(socket1, 0);
+ ASSERT_GE(socket1, 0);
init_sockets.push_back(socket1);
// Create socket #2
@@ -388,7 +471,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
ASSERT_NO_THROW(
socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
);
- ASSERT_GT(socket2, 0);
+ ASSERT_GE(socket2, 0);
init_sockets.push_back(socket2);
// Get loopback interface. If we don't find one we are unable to run
@@ -469,13 +552,13 @@ TEST_F(IfaceMgrTest, sockets6) {
// Bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
- EXPECT_GT(socket1, 0); // socket > 0
+ EXPECT_GE(socket1, 0); // socket >= 0
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
// Bind unicast socket to port 10548
int socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10548);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// Removed code for binding socket twice to the same address/port
// as it caused problems on some platforms (e.g. Mac OS X)
@@ -509,8 +592,8 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET6);
);
- // Socket descriptor must be positive integer
- EXPECT_GT(socket1, 0);
+ // Socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
close(socket1);
// Open v4 socket on loopback interface and bind to different port
@@ -518,8 +601,8 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
EXPECT_NO_THROW(
socket2 = ifacemgr->openSocketFromIface(LOOPBACK, PORT2, AF_INET);
);
- // socket descriptor must be positive integer
- EXPECT_GT(socket2, 0);
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket2, 0);
close(socket2);
// Close sockets here because the following tests will want to
@@ -546,8 +629,8 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromAddress(loAddr6, PORT1);
);
- // socket descriptor must be positive integer
- EXPECT_GT(socket1, 0);
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
// Open v4 socket on loopback interface and bind to different port
int socket2 = 0;
@@ -556,7 +639,7 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
socket2 = ifacemgr->openSocketFromAddress(loAddr, PORT2);
);
// socket descriptor must be positive integer
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// Close sockets here because the following tests will want to
// open sockets on the same ports.
@@ -583,7 +666,7 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromRemoteAddress(loAddr6, PORT1);
);
- EXPECT_GT(socket1, 0);
+ EXPECT_GE(socket1, 0);
// Open v4 socket to connect to remote address.
int socket2 = 0;
@@ -591,25 +674,17 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
EXPECT_NO_THROW(
socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// Close sockets here because the following tests will want to
// open sockets on the same ports.
ifacemgr->closeSockets();
- // The following test is currently disabled for OSes other than
- // Linux because interface detection is not implemented on them.
- // @todo enable this test for all OSes once interface detection
- // is implemented.
-#if defined(OS_LINUX)
- // Open v4 socket to connect to broadcast address.
- int socket3 = 0;
- IOAddress bcastAddr("255.255.255.255");
- EXPECT_NO_THROW(
- socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
- );
- EXPECT_GT(socket3, 0);
-#endif
+ // There used to be a check here that verified the ability to open
+ // suitable socket for sending broadcast request. However,
+ // there is no guarantee for such test to work on all systems
+ // because some systems may have no broadcast capable interfaces at all.
+ // Thus, this check has been removed.
// Do not call closeSockets() because it is called by IfaceMgr's
// virtual destructor.
@@ -628,12 +703,12 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
// bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
- EXPECT_GT(socket1, 0); // socket > 0
+ EXPECT_GE(socket1, 0); // socket > 0
// expect success. This address/port is already bound, but
// we are using SO_REUSEADDR, so we can bind it twice
int socket2 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// there's no good way to test negative case here.
// we would need non-multicast interface. We will be able
@@ -659,8 +734,8 @@ TEST_F(IfaceMgrTest, sendReceive6) {
socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10546);
);
- EXPECT_GT(socket1, 0);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket1, 0);
+ EXPECT_GE(socket2, 0);
// prepare dummy payload
@@ -714,14 +789,12 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// let's assume that every supported OS have lo interface
IOAddress loAddr("127.0.0.1");
- int socket1 = 0, socket2 = 0;
+ int socket1 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
- socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000 + 1);
);
EXPECT_GE(socket1, 0);
- EXPECT_GE(socket2, 0);
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
@@ -754,7 +827,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
boost::shared_ptr<Pkt4> rcvPkt;
- EXPECT_EQ(true, ifacemgr->send(sendPkt));
+ EXPECT_NO_THROW(ifacemgr->send(sendPkt));
ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
ASSERT_TRUE(rcvPkt); // received our own packet
@@ -789,10 +862,30 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// assume the one or the other will always be chosen for sending data. We should
// skip checking source port of sent address.
- // try to receive data over the closed socket. Closed socket's descriptor is
- // still being hold by IfaceMgr which will try to use it to receive data.
+ // Close the socket. Further we will test if errors are reported
+ // properly on attempt to use closed soscket.
close(socket1);
+
+// Warning: kernel bug on FreeBSD. The following code checks that attempt to
+// read through invalid descriptor will result in exception. The reason why
+// this failure is expected is that select() function should result in EBADF
+// error when invalid descriptor is passed to it. In particular, closed socket
+// descriptor is invalid. On the following OS:
+//
+// 8.1-RELEASE FreeBSD 8.1-RELEASE #0: Mon Jul 19 02:55:53 UTC 2010
+//
+// calling select() using invalid descriptor results in timeout and eventually
+// value of 0 is returned. This has been identified and reported as a bug in
+// FreeBSD: http://www.freebsd.org/cgi/query-pr.cgi?pr=155606
+//
+// @todo: This part of the test is currently disabled on all BSD systems as it was
+// the quick fix. We need a more elegant (config-based) solution to disable
+// this check on affected systems only. The ticket has been submited for this
+// work: http://bind10.isc.org/ticket/2971
+#ifndef OS_BSD
EXPECT_THROW(ifacemgr->receive4(10), SocketReadError);
+#endif
+
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
}
@@ -826,8 +919,80 @@ TEST_F(IfaceMgrTest, setPacketFilter) {
EXPECT_TRUE(custom_packet_filter->open_socket_called_);
// This function always returns fake socket descriptor equal to 1024.
EXPECT_EQ(1024, socket1);
+
+ // Replacing current packet filter object while there are IPv4
+ // sockets open is not allowed!
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ PacketFilterChangeDenied);
+
+ // So, let's close the open IPv4 sockets and retry. Now it should succeed.
+ iface_mgr->closeSockets(AF_INET);
+ EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
}
+#if defined OS_LINUX
+
+// This Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // There is working implementation of direct responses on Linux
+ // in PktFilterLPF. It uses Linux Packet Filtering as underlying
+ // mechanism. When direct responses are desired the object of
+ // this class should be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report that direct responses are supported.
+ EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
+}
+
+#else
+
+// This non-Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired. Since direct responses aren't supported
+// on systems other than Linux the function under test should always
+// set object of PktFilterInet type as current Packet Filter. This
+// object does not support direct responses. Once implementation is
+// added on non-Linux systems the OS specific version of the test
+// will be removed.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // On non-Linux systems, we are missing the direct traffic
+ // implementation. Therefore, we expect that PktFilterInet
+ // object will be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report lack of direct response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+}
+
+#endif
TEST_F(IfaceMgrTest, socket4) {
@@ -842,7 +1007,7 @@ TEST_F(IfaceMgrTest, socket4) {
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
);
- EXPECT_GT(socket1, 0);
+ EXPECT_GE(socket1, 0);
Pkt4 pkt(DHCPDISCOVER, 1234);
pkt.setIface(LOOPBACK);
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index f924427..d523158 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -24,6 +24,7 @@
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -372,7 +373,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
/// is no restriction on the data length being carried by them.
/// For simplicity, we assign data of the length 3 for each
/// of them.
-static uint8_t v4Opts[] = {
+static uint8_t v4_opts[] = {
12, 3, 0, 1, 2, // Hostname
60, 3, 10, 11, 12, // Class Id
14, 3, 20, 21, 22, // Merit Dump File
@@ -403,18 +404,18 @@ TEST_F(LibDhcpTest, packOptions4) {
opts.insert(make_pair(opt1->getType(), opt4));
opts.insert(make_pair(opt1->getType(), opt5));
- vector<uint8_t> expVect(v4Opts, v4Opts + sizeof(v4Opts));
+ vector<uint8_t> expVect(v4_opts, v4_opts + sizeof(v4_opts));
OutputBuffer buf(100);
EXPECT_NO_THROW(LibDHCP::packOptions(buf, opts));
- ASSERT_EQ(buf.getLength(), sizeof(v4Opts));
- EXPECT_EQ(0, memcmp(v4Opts, buf.getData(), sizeof(v4Opts)));
+ ASSERT_EQ(buf.getLength(), sizeof(v4_opts));
+ EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts)));
}
TEST_F(LibDhcpTest, unpackOptions4) {
- vector<uint8_t> v4packed(v4Opts, v4Opts + sizeof(v4Opts));
+ vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
isc::dhcp::Option::OptionCollection options; // list of options
ASSERT_NO_THROW(
@@ -423,38 +424,43 @@ TEST_F(LibDhcpTest, unpackOptions4) {
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
ASSERT_FALSE(x == options.end()); // option 1 should exist
- EXPECT_EQ(12, x->second->getType()); // this should be option 12
- ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+2, 3)); // data len=3
+ // Option 12 holds a string so let's cast it to an appropriate type.
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3
x = options.find(60);
ASSERT_FALSE(x == options.end()); // option 2 should exist
EXPECT_EQ(60, x->second->getType()); // this should be option 60
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+7, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3
x = options.find(14);
ASSERT_FALSE(x == options.end()); // option 3 should exist
- EXPECT_EQ(14, x->second->getType()); // this should be option 14
- ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+12, 3)); // data len=3
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3
x = options.find(254);
ASSERT_FALSE(x == options.end()); // option 3 should exist
EXPECT_EQ(254, x->second->getType()); // this should be option 254
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+17, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3
x = options.find(128);
ASSERT_FALSE(x == options.end()); // option 3 should exist
EXPECT_EQ(128, x->second->getType()); // this should be option 254
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+22, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3
x = options.find(0);
EXPECT_TRUE(x == options.end()); // option 0 not found
@@ -532,7 +538,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
// It will be used to create most of the options.
std::vector<uint8_t> buf(48, 1);
OptionBufferConstIter begin = buf.begin();
- OptionBufferConstIter end = buf.begin();
+ OptionBufferConstIter end = buf.end();
LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end,
typeid(OptionCustom));
@@ -568,25 +574,25 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option4AddrLst));
LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2,
typeid(OptionInt<uint16_t>));
LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end,
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end,
typeid(OptionCustom));
@@ -652,7 +658,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end,
typeid(Option4AddrLst));
@@ -674,7 +680,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionInt<uint8_t>));
LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end,
typeid(Option4AddrLst));
@@ -701,7 +707,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2,
typeid(OptionInt<uint16_t>));
@@ -719,7 +725,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option));
LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end,
typeid(Option));
@@ -908,10 +914,10 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
typeid(Option6AddrLst));
LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end,
typeid(OptionIntArray<uint16_t>));
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 2a9f771..c04bbc2 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -1324,7 +1324,7 @@ TEST_F(OptionCustomTest, unpack) {
// The purpose of this test is to verify that new data can be set for
// a custom option.
-TEST_F(OptionCustomTest, setData) {
+TEST_F(OptionCustomTest, initialize) {
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
// Initialize reference data.
@@ -1370,7 +1370,7 @@ TEST_F(OptionCustomTest, setData) {
}
// Replace the option data.
- ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
+ ASSERT_NO_THROW(option->initialize(buf.begin(), buf.end()));
// Now we should have only 2 data fields.
ASSERT_EQ(2, option->getDataFieldsNum());
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 174bafb..f7602a6 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -25,6 +25,7 @@
#include <dhcp/option_definition.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp>
@@ -936,11 +937,10 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
);
ASSERT_TRUE(option_v6);
- 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());
- EXPECT_TRUE(std::equal(ref_data.begin(), ref_data.end(), data.begin()));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionString));
+ OptionStringPtr option_v6_string =
+ boost::static_pointer_cast<OptionString>(option_v6);
+ EXPECT_TRUE(values[0] == option_v6_string->getValue());
}
// The purpose of this test is to check that non-integer data type can't
diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc
new file mode 100644
index 0000000..0be3c3d
--- /dev/null
+++ b/src/lib/dhcp/tests/option_string_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option_string.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionString test class.
+class OptionStringTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the test buffer with some data.
+ OptionStringTest() {
+ std::string test_string("This is a test string");
+ buf_.assign(test_string.begin(), test_string.end());
+ }
+
+ OptionBuffer buf_;
+
+};
+
+// This test verifies that the constructor which creates an option instance
+// from a string value will create it properly.
+TEST_F(OptionStringTest, constructorFromString) {
+ const std::string optv4_value = "some option";
+ OptionString optv4(Option::V4, 123, optv4_value);
+ EXPECT_EQ(Option::V4, optv4.getUniverse());
+ EXPECT_EQ(123, optv4.getType());
+ EXPECT_EQ(optv4_value, optv4.getValue());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + optv4_value.size(), optv4.len());
+
+ // Do another test with the same constructor to make sure that
+ // different set of parameters would initialize the class members
+ // to different values.
+ const std::string optv6_value = "other option";
+ OptionString optv6(Option::V6, 234, optv6_value);
+ EXPECT_EQ(Option::V6, optv6.getUniverse());
+ EXPECT_EQ(234, optv6.getType());
+ EXPECT_EQ("other option", optv6.getValue());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + optv6_value.size(), optv6.len());
+
+ // Check that an attempt to use empty string in the constructor
+ // will result in an exception.
+ EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange);
+}
+
+// This test verifies that the constructor which creates an option instance
+// from a buffer, holding option payload, will create it properly.
+// This function calls unpack() internally thus test test is considered
+// to cover testing of unpack() functionality.
+TEST_F(OptionStringTest, constructorFromBuffer) {
+ // Attempt to create an option using empty buffer should result in
+ // an exception.
+ EXPECT_THROW(
+ OptionString(Option::V4, 234, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Declare option as a scoped pointer here so as its scope is
+ // function wide. The initialization (constructor invocation)
+ // is pushed to the ASSERT_NO_THROW macro below, as it may
+ // throw exception if buffer is truncated.
+ boost::scoped_ptr<OptionString> optv4;
+ ASSERT_NO_THROW(
+ optv4.reset(new OptionString(Option::V4, 234, buf_.begin(), buf_.end()));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv4);
+
+ // Test the instance of the created option.
+ const std::string optv4_value = "This is a test string";
+ EXPECT_EQ(Option::V4, optv4->getUniverse());
+ EXPECT_EQ(234, optv4->getType());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + buf_.size(), optv4->len());
+ EXPECT_EQ(optv4_value, optv4->getValue());
+
+ // Do the same test for V6 option.
+ boost::scoped_ptr<OptionString> optv6;
+ ASSERT_NO_THROW(
+ // Let's reduce the size of the buffer by one byte and see if our option
+ // will absorb this little change.
+ optv6.reset(new OptionString(Option::V6, 123, buf_.begin(), buf_.end() - 1));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv6);
+
+ // Test the instance of the created option.
+ const std::string optv6_value = "This is a test strin";
+ EXPECT_EQ(Option::V6, optv6->getUniverse());
+ EXPECT_EQ(123, optv6->getType());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + buf_.size() - 1, optv6->len());
+ EXPECT_EQ(optv6_value, optv6->getValue());
+}
+
+// This test verifies that the current option value can be overriden
+// with new value, using setValue method.
+TEST_F(OptionStringTest, setValue) {
+ // Create an instance of the option and set some initial value.
+ OptionString optv4(Option::V4, 123, "some option");
+ EXPECT_EQ("some option", optv4.getValue());
+ // Replace the value with the new one, and make sure it has
+ // been successful.
+ EXPECT_NO_THROW(optv4.setValue("new option value"));
+ EXPECT_EQ("new option value", optv4.getValue());
+ // Try to set to an empty string. It should throw exception.
+ EXPECT_THROW(optv4.setValue(""), isc::OutOfRange);
+}
+
+// This test verifies that the pack function encodes the option in
+// a on-wire format properly.
+TEST_F(OptionStringTest, pack) {
+ // Create an instance of the option.
+ std::string option_value("sample option value");
+ OptionString optv4(Option::V4, 123, option_value);
+ // Encode the option in on-wire format.
+ OutputBuffer buf(Option::OPTION4_HDR_LEN);
+ EXPECT_NO_THROW(optv4.pack(buf));
+
+ // Sanity check the length of the buffer.
+ ASSERT_EQ(Option::OPTION4_HDR_LEN + option_value.length(),
+ buf.getLength());
+ // Copy the contents of the OutputBuffer to InputBuffer because
+ // the latter has API to read data from it.
+ InputBuffer test_buf(buf.getData(), buf.getLength());
+ // First byte holds option code.
+ EXPECT_EQ(123, test_buf.readUint8());
+ // Second byte holds option length.
+ EXPECT_EQ(option_value.size(), test_buf.readUint8());
+ // Read the option data.
+ std::vector<uint8_t> data;
+ test_buf.readVector(data, test_buf.getLength() - test_buf.getPosition());
+ // And create a string from it.
+ std::string test_string(data.begin(), data.end());
+ // This string should be equal to the string used to create
+ // option's instance.
+ EXPECT_TRUE(option_value == test_string);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 5c95f7d..a108930 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/option_string.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -549,17 +550,25 @@ TEST(Pkt4Test, unpackOptions) {
boost::shared_ptr<Option> x = pkt->getOption(12);
ASSERT_TRUE(x); // option 1 should exist
- EXPECT_EQ(12, x->getType()); // this should be option 12
- ASSERT_EQ(3, x->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 2, 3)); // data len=3
+ // Option 12 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4Opts + 2, 3)); // data len=3
x = pkt->getOption(14);
- ASSERT_TRUE(x); // option 13 should exist
- EXPECT_EQ(14, x->getType()); // this should be option 13
- ASSERT_EQ(3, x->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 7, 3)); // data len=3
+ ASSERT_TRUE(x); // option 14 should exist
+ // Option 14 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4Opts + 7, 3)); // data len=3
x = pkt->getOption(60);
ASSERT_TRUE(x); // option 60 should exist
@@ -644,4 +653,49 @@ TEST(Pkt4Test, hwaddr) {
EXPECT_TRUE(hwaddr == pkt->getHWAddr());
}
+// This test verifies that the packet remte and local HW address can
+// be set and returned.
+TEST(Pkt4Test, hwaddrSrcRemote) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
+ const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
+ const uint8_t hw_type = 123;
+
+ HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type));
+ HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type));
+
+ // Check that we can set the local address.
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr));
+ EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr());
+
+ // Check that we can set the remote address.
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr));
+ EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr());
+
+ // Can't set the NULL addres.
+ EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue);
+ EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue);
+
+ // Test alternative way to set local address.
+ const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 };
+ std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2));
+ const uint8_t hw_type2 = 234;
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec));
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(local_addr);
+ EXPECT_EQ(hw_type2, local_addr->htype_);
+ EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(),
+ local_addr->hwaddr_.begin()));
+
+ // Set remote address.
+ const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 };
+ std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2));
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec));
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(remote_addr);
+ EXPECT_EQ(hw_type2, remote_addr->htype_);
+ EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(),
+ remote_addr->hwaddr_.begin()));
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
new file mode 100644
index 0000000..a80c064
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterInetTest : public ::testing::Test {
+public:
+ PktFilterInetTest() {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ }
+
+ ~PktFilterInetTest() {
+ // Cleanup after each test. This guarantees
+ // that the socket does not hang after a test.
+ close(socket_);
+ }
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+
+
+ }
+ }
+
+ std::string ifname_; ///< Loopback interface name
+ uint16_t ifindex_; ///< Loopback interface index.
+ int socket_; ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterInet class reports its lack
+// of capability to send packets to the host having no IP address
+// assigned.
+TEST_F(PktFilterInetTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterInet pkt_filter;
+ // This Packet Filter class does not support direct responses
+ // under any conditions.
+ EXPECT_FALSE(pkt_filter.isDirectResponseSupported());
+}
+
+// This test verifies that the INET datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInetTest, openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterInet pkt_filter;
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ // Check that socket has been opened.
+ ASSERT_GE(socket_, 0);
+
+ // Verify that the socket belongs to AF_INET family.
+ sockaddr_in sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_INET, sock_address.sin_family);
+
+ // Verify that the socket is bound the appropriate address.
+ const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
+ EXPECT_EQ("127.0.0.1", bind_addr);
+
+ // Verify that the socket is bound to appropriate port.
+ EXPECT_EQ(PORT, ntohs(sock_address.sin_port));
+
+ // Verify that the socket has SOCK_DGRAM type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+// This test verifies that the packet is correctly sent over the INET
+// datagram socket.
+TEST_F(PktFilterInetTest, send) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(socket_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ // Create the DHCPv4 packet from the received data.
+ Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies that the DHCPv4 packet is correctly received via
+// INET datagram socket and that it matches sent packet.
+TEST_F(PktFilterInetTest, receive) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Receive the packet.
+ SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
new file mode 100644
index 0000000..c072488
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <linux/if_packet.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterLPFTest : public ::testing::Test {
+public:
+ PktFilterLPFTest() {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ }
+
+ ~PktFilterLPFTest() {
+ // Cleanup after each test. This guarantees
+ // that the socket does not hang after a test.
+ close(socket_);
+ }
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+
+
+ }
+ }
+
+ std::string ifname_; ///< Loopback interface name
+ uint16_t ifindex_; ///< Loopback interface index.
+ int socket_; ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterLPF class reports its capability
+// to send packets to the host having no IP address assigned.
+TEST_F(PktFilterLPFTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterLPF pkt_filter;
+ // Must support direct responses.
+ EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
+}
+
+// All tests below require root privileges to execute successfully. If
+// they are run as non-root user they will fail due to insufficient privileges
+// to open raw network sockets. Therefore, they should remain disabled by default
+// and "DISABLED_" tags should not be removed. If one is willing to run these
+// tests please run "make check" as root and enable execution of disabled tests
+// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
+// to run tests from this particular file, set the GTEST_FILTER environmental
+// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
+// setting.
+
+// This test verifies that the raw AF_PACKET family socket can
+// be opened and bound to the specific interface.
+TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterLPF pkt_filter;
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ // Check that socket has been opened.
+ ASSERT_GE(socket_, 0);
+
+ // Verify that the socket belongs to AF_PACKET family.
+ sockaddr_ll sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_PACKET, sock_address.sll_family);
+
+ // Verify that the socket is bound to appropriate interface.
+ EXPECT_EQ(ifindex_, sock_address.sll_ifindex);
+
+ // Verify that the socket has SOCK_RAW type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_RAW, sock_type);
+}
+
+// This test verifies correctness of sending DHCP packet through the raw
+// socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_send) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ // By setting the local address to broadcast we simulate the
+ // typical scenario when client's request was send to broadcast
+ // address and server by default used it as a source address
+ // in its response. The send() function should be able to detect
+ // it and correct the source address.
+ pkt->setLocalAddr(IOAddress("255.255.255.255"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(socket_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ InputBuffer buf(rcv_buf, result);
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Create the DHCPv4 packet from the received data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+ Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies correctness of reception of the DHCP packet over
+// raw socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_receive) {
+
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Receive the packet.
+ SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
new file mode 100644
index 0000000..eee98e6
--- /dev/null
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -0,0 +1,390 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+ /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+ // IPv4 header to be used to calculate checksum.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ 0x06, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xac, 0x10, 0x0a, 0x63, // Source IP address.
+ 0xac, 0x10, 0x0a, 0x0c // Destination IP address.
+ };
+ // Calculate size of the header array.
+ const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+ // Get the actual checksum.
+ uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+ // The 0xb1e6 value has been calculated by other means.
+ EXPECT_EQ(0xb1e6, chksum);
+ // Tested function may also take the initial value of the sum.
+ // Let's set it to 2 and see whether it is included in the
+ // calculation.
+ chksum = ~calcChecksum(hdr, hdr_size, 2);
+ // The checkum value should change.
+ EXPECT_EQ(0xb1e4, chksum);
+}
+
+// The purpose of this test is to verify that the Ethernet frame header
+// can be decoded correctly. In particular it verifies that the source
+// HW address can be extracted from it.
+TEST(ProtocolUtilTest, decodeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Prepare a buffer holding Ethernet frame header and 4 bytes of
+ // dummy data.
+ OutputBuffer buf(1);
+ buf.writeData(dest_hw_addr, sizeof(dest_hw_addr));
+ buf.writeData(src_hw_addr, sizeof(src_hw_addr));
+ buf.writeUint16(ETHERNET_TYPE_IP);
+ // Append dummy data. We will later check that this data is not
+ // removed or corrupted when reading the ethernet header.
+ buf.writeUint32(0x01020304);
+
+ // Create a buffer with truncated ethernet frame header..
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6);
+ // But provide valid packet object to make sure that the function
+ // under test does not throw due to NULL pointer packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because header data is truncated.
+ EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt),
+ InvalidPacketHeader);
+
+ // Get not truncated buffer.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // But provide NULL packet object instead.
+ pkt.reset();
+ // It should throw again but a different exception.
+ EXPECT_THROW(decodeEthernetHeader(in_buf, pkt),
+ BadValue);
+ // Now provide, correct data.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ // It should not throw now.
+ ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt));
+ // Verify that the destination HW address has been initialized...
+ HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(checked_dest_hwaddr);
+ // and is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_);
+ ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr),
+ checked_dest_hwaddr->hwaddr_.begin()));
+
+ // Verify that the HW address of the source has been initialized.
+ HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(checked_src_hwaddr);
+ // And that it is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_);
+ ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr),
+ checked_src_hwaddr->hwaddr_.begin()));
+
+ // The entire ethernet packet header should have been read. This means
+ // that the internal buffer pointer should now point to its tail.
+ ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition());
+ // And the dummy data should be still readable and correct.
+ uint32_t dummy_data = in_buf.readUint32();
+ EXPECT_EQ(0x01020304, dummy_data);
+}
+
+/// The purpose of this test is to verify that the IP and UDP header
+/// is decoded correctly and appropriate values of IP addresses and
+/// ports are assigned to a Pkt4 object.
+TEST(ProtocolUtilTest, decodeIpUdpHeader) {
+ // IPv4 header to be parsed.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ IPPROTO_UDP, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xc0, 0x00, 0x02, 0x63, // Source IP address.
+ 0xc0, 0x00, 0x02, 0x0c, // Destination IP address.
+ 0x27, 0x54, // Source port
+ 0x27, 0x53, // Destination port
+ 0x00, 0x08, // UDP length
+ 0x00, 0x00 // Checksum
+ };
+
+ // Write header data to the buffer.
+ OutputBuffer buf(1);
+ buf.writeData(hdr, sizeof(hdr));
+ // Append some dummy data.
+ buf.writeUint32(0x01020304);
+
+ // Create an input buffer holding truncated headers.
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10);
+ // Create non NULL Pkt4 object to make sure that the function under
+ // test does not throw due to invalid Pkt4 object.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because buffer holds truncated data.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader);
+
+ // Create a valid input buffer (not truncated).
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // Set NULL Pkt4 object to verify that function under test will
+ // return exception as expected.
+ pkt.reset();
+ // And check whether it throws exception.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue);
+
+ // Now, let's provide valid arguments and make sure it doesn't throw.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ ASSERT_TRUE(pkt);
+ EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt));
+
+ // Verify the source address and port.
+ EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText());
+ EXPECT_EQ(10068, pkt->getRemotePort());
+
+ // Verify the destination address and port.
+ EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText());
+ EXPECT_EQ(10067, pkt->getLocalPort());
+
+ // Verify that the dummy data has not been corrupted and that the
+ // internal read pointer has been moved to the tail of the UDP
+ // header.
+ ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition());
+ EXPECT_EQ(0x01020304, in_buf.readUint32());
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+
+ // Set invalid length (7) of the hw address.
+ HWAddrPtr remote_hw_addr(new HWAddr(&std::vector<uint8_t>(1, 7)[0], 7, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+ // HW address is too long, so it should fail.
+ EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue);
+
+ // Finally, set a valid HW address.
+ remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+ // Create DHCPv4 packet. Some values held by this object are
+ // used by the utility function under test to figure out the
+ // contents of the IP and UDP headers, e.g. source and
+ // destination IP address or port number.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set local and remote address and port.
+ pkt->setLocalAddr(IOAddress("192.0.2.1"));
+ pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+ pkt->setLocalPort(DHCP4_SERVER_PORT);
+ pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+ // Pack the contents of the packet.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Create output buffer. The headers will be written to it.
+ OutputBuffer buf(1);
+ // Write some dummy data to the buffer. We will check that the
+ // function under test appends to this data, not overrides it.
+ buf.writeUint16(0x0102);
+
+ // Write both IP and UDP headers.
+ writeIpUdpHeader(pkt, buf);
+
+ // The resulting size of the buffer must be 30. The 28 bytes are
+ // consumed by the IP and UDP headers. The other 2 bytes are dummy
+ // data at the beginning of the buffer.
+ ASSERT_EQ(30, buf.getLength());
+
+ // Make sure that the existing data in the buffer was not corrupted
+ // by the function under test.
+ EXPECT_EQ(0x01, buf[0]);
+ EXPECT_EQ(0x02, buf[1]);
+
+ // Copy the contents of the buffer to InputBuffer object. This object
+ // exposes convenient functions for reading.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+
+ // Check dummy data.
+ uint16_t dummy_data = in_buf.readUint16();
+ EXPECT_EQ(0x0102, dummy_data);
+
+ // The IP version and IHL are stored in the same octet (4 bits each).
+ uint8_t ver_len = in_buf.readUint8();
+ // The most significant bits define IP version.
+ uint8_t ip_ver = ver_len >> 4;
+ EXPECT_EQ(4, ip_ver);
+ // The least significant bits define header length (in 32-bits chunks).
+ uint8_t ip_len = ver_len & 0x0F;
+ EXPECT_EQ(5, ip_len);
+
+ // Get Differentiated Services Codepoint and Explicit Congestion
+ // Notification field.
+ uint8_t dscp_ecn = in_buf.readUint8();
+ EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+ // Total length of IP packet. Includes UDP header and payload.
+ uint16_t total_len = in_buf.readUint16();
+ EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+ // Identification field.
+ uint16_t ident = in_buf.readUint16();
+ EXPECT_EQ(0, ident);
+
+ // Fragmentation.
+ uint16_t fragment = in_buf.readUint16();
+ // Setting second most significant bit means no fragmentation.
+ EXPECT_EQ(0x4000, fragment);
+
+ // Get TTL
+ uint8_t ttl = in_buf.readUint8();
+ // Expect non-zero TTL.
+ EXPECT_GE(ttl, 1);
+
+ // Protocol type is UDP.
+ uint8_t proto = in_buf.readUint8();
+ EXPECT_EQ(IPPROTO_UDP, proto);
+
+ // Check that the checksum is correct. The reference checksum value
+ // has been calculated manually.
+ uint16_t ip_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x755c, ip_checksum);
+
+ // Validate source address.
+ // Initializing it to IPv6 address guarantees that it is not initialized
+ // to the value that we expect to be read from a header since the value
+ // read from a header will be IPv4.
+ IOAddress src_addr("::1");
+ // Read src address as an array of bytes because it is easely convertible
+ // to IOAddress object.
+ uint8_t src_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(src_addr_data, 4);
+ src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText());
+
+ // Validate destination address.
+ IOAddress dest_addr("::1");
+ uint8_t dest_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(dest_addr_data, 4);
+ dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText());
+
+ // UDP header starts here.
+
+ // Check source port.
+ uint16_t src_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+ // Check destination port.
+ uint16_t dest_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+ // UDP header and data length.
+ uint16_t udp_len = in_buf.readUint16();
+ EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+ // Verify UDP checksum. The reference checksum has been calculated manually.
+ uint16_t udp_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x8817, udp_checksum);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index 43594fd..174b5e2 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -26,7 +26,7 @@
- The MySQL lease manager uses the freely available MySQL as its backend
database. This is not included in BIND 10 DHCP by default:
- the --with-dhcp-mysql switch must be supplied to "configure" for support
+ the \--with-dhcp-mysql switch must be supplied to "configure" for support
to be compiled into the software.
- Memfile is an in-memory lease database, with (currently) nothing being
written to persistent storage. The long-term plans for the backend do
@@ -83,9 +83,42 @@
@subsection dhcp-mysql-unittest MySQL
- A database called <i>keatest</i> needs to be set up using the MySQL
- <b>CREATE DATABASE</b> command. A database user, also called <i>keatest</i>
- (with a password <i>keatest</i>) must be given full privileges in that
- database. The unit tests create the schema in the database before each test
- and delete it afterwards.
+ A database called <i>keatest</i> must be created. A database user, also called
+ <i>keatest</i> (and with a password <i>keatest</i>) must also be created and
+ be given full privileges in that database. The unit tests create the schema
+ in the database before each test and delete it afterwards.
+
+ In detail, the steps to create the database and user are:
+
+ -# Log into MySQL as root:
+ @verbatim
+ % mysql -u root -p
+ Enter password:
+ :
+ mysql>@endverbatim\n
+ -# Create the test database. This must be called "keatest":
+ @verbatim
+ mysql> CREATE DATABASE keatest;
+ mysql>@endverbatim\n
+ -# Create the user under which the test client will connect to the database
+ (the apostrophes around the words <i>keatest</i> and <i>localhost</i> are
+ required):
+ @verbatim
+ mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest';
+ mysql>@endverbatim\n
+ -# Grant the created user permissions to access the <i>keatest</i> database
+ (again, the apostrophes around the words <i>keatest</i> and <i>localhost</i>
+ are required):
+ @verbatim
+ mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost';
+ mysql>@endverbatim\n
+ -# Exit MySQL:
+ @verbatim
+ mysql> quit
+ Bye
+ %@endverbatim
+
+ The unit tests are run automatically when "make check" is executed (providing
+ that BIND 10 has been build with the \--with-dhcp-mysql switch (see the installation
+ section in the <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND 10 Guide</a>).
*/
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index a49f72f..3fd3b33 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -24,39 +24,6 @@ from os.path import getmtime
import re
import sys
-# new_rdata_factory_users[] is a list of tuples of the form (rrtype,
-# rrclass). Items in the list use the (new) RdataFactory class, and
-# items which are not in the list use OldRdataFactory class.
-# Note: rrtype and rrclass must be specified in lowercase in
-# new_rdata_factory_users.
-#
-# Example:
-# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('a', 'in'),
- ('aaaa', 'in'),
- ('afsdb', 'generic'),
- ('cname', 'generic'),
- ('dhcid', 'in'),
- ('dlv', 'generic'),
- ('dname', 'generic'),
- ('dnskey', 'generic'),
- ('ds', 'generic'),
- ('hinfo', 'generic'),
- ('naptr', 'generic'),
- ('mx', 'generic'),
- ('ns', 'generic'),
- ('nsec', 'generic'),
- ('nsec3', 'generic'),
- ('nsec3param', 'generic'),
- ('opt', 'generic'),
- ('ptr', 'generic'),
- ('rrsig', 'generic'),
- ('soa', 'generic'),
- ('spf', 'generic'),
- ('srv', 'in'),
- ('txt', 'generic')
- ]
-
re_typecode = re.compile('([\da-z\-]+)_(\d+)')
classcode2txt = {}
typecode2txt = {}
@@ -66,7 +33,7 @@ meta_types = {
# Real meta types. We won't have Rdata implement for them, but we need
# RRType constants.
'251': 'ixfr', '252': 'axfr', '255': 'any',
- # Obsolete types. We probalby won't implement Rdata for them, but it's
+ # Obsolete types. We probably won't implement Rdata for them, but it's
# better to have RRType constants.
'3': 'md', '4': 'mf', '7': 'mb', '8': 'mg', '9': 'mr', '30': 'nxt',
'38': 'a6', '254': 'maila',
@@ -375,28 +342,15 @@ def generate_rrparam(fileprefix, basemtime):
indent = ' ' * 8
typeandclassparams += indent
- # By default, we use OldRdataFactory (see bug #2497). If you
- # want to pick RdataFactory for a particular type, add it to
- # new_rdata_factory_users. Note that we explicitly generate (for
- # optimization) class-independent ("generic") factories for class IN
- # for optimization.
- if (((type_txt.lower(), class_txt.lower()) in
- new_rdata_factory_users) or
- ((class_txt.lower() == 'in') and
- ((type_txt.lower(), 'generic') in new_rdata_factory_users))):
- rdf_class = 'RdataFactory'
- else:
- rdf_class = 'OldRdataFactory'
-
if class_tuple[1] != 'generic':
typeandclassparams += 'add("' + type_utxt + '", '
typeandclassparams += str(type_code) + ', "' + class_utxt
typeandclassparams += '", ' + str(class_code)
- typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
else:
typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
- typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
typeandclassparams += indent + '// Meta and non-implemented RR types\n'
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index ff848fa..4e86cf9 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,26 +19,28 @@
#include <boost/lexical_cast.hpp>
#include <util/buffer.h>
-#include <util/strutil.h>
#include <util/encode/base64.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rcode.h>
#include <dns/tsigerror.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
using namespace isc::util::encode;
-using namespace isc::util::str;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-/// This is a straightforward representation of TSIG RDATA fields.
-struct TSIG::TSIGImpl {
+// straightforward representation of TSIG RDATA fields
+struct TSIGImpl {
TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
vector<uint8_t>& mac, uint16_t original_id, uint16_t error,
vector<uint8_t>& other_data) :
@@ -68,99 +70,184 @@ struct TSIG::TSIGImpl {
const vector<uint8_t> other_data_;
};
+// helper function for string and lexer constructors
+TSIGImpl*
+TSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const Name& algorithm =
+ createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME());
+
+ const string& time_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint64_t time_signed;
+ try {
+ time_signed = boost::lexical_cast<uint64_t>(time_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Time");
+ }
+ if ((time_signed >> 48) != 0) {
+ isc_throw(InvalidRdataText, "TSIG Time out of range");
+ }
+
+ const uint32_t fudge = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fudge > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Fudge out of range");
+ }
+ const uint32_t macsize =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (macsize > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size out of range");
+ }
+
+ const string& mac_txt = (macsize > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> mac;
+ decodeBase64(mac_txt, mac);
+ if (mac.size() != macsize) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size and data are inconsistent");
+ }
+
+ const uint32_t orig_id =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (orig_id > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Original ID out of range");
+ }
+
+ const string& error_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "NOERROR") {
+ error = Rcode::NOERROR_CODE;
+ } else if (error_txt == "BADSIG") {
+ error = TSIGError::BAD_SIG_CODE;
+ } else if (error_txt == "BADKEY") {
+ error = TSIGError::BAD_KEY_CODE;
+ } else if (error_txt == "BADTIME") {
+ error = TSIGError::BAD_TIME_CODE;
+ } else {
+ /// we cast to uint32_t and range-check, because casting directly to
+ /// uint16_t will convert negative numbers to large positive numbers
+ try {
+ error = boost::lexical_cast<uint32_t>(error_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Error");
+ }
+ if (error > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Error out of range");
+ }
+ }
+
+ const uint32_t otherlen =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (otherlen > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Other Len out of range");
+ }
+ const string otherdata_txt = (otherlen > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+ if (other_data.size() != otherlen) {
+ isc_throw(InvalidRdataText,
+ "TSIG Other Data length does not match Other Len");
+ }
+ // RFC2845 says Other Data is "empty unless Error == BADTIME".
+ // However, we don't enforce that.
+
+ return (new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
+ error, other_data));
+}
+
/// \brief Constructor from string.
///
+/// The given string must represent a valid TSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
/// \c tsig_str must be formatted as follows:
-/// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
+/// \code <Algorithm Name> <Time Signed> <Fudge> <MAC Size> [<MAC>]
+/// <Original ID> <Error> <Other Len> [<Other Data>]
/// \endcode
-/// where
-/// - <Alg> is a valid textual representation of domain name.
-/// - <Time> is an unsigned 48-bit decimal integer.
-/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned
-/// 16-bit decimal
-/// integer.
-/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic
-/// for the Error field specified in RFC2845. Currently, "BADSIG", "BADKEY",
-/// and "BADTIME" are supported (case sensitive). In future versions
-/// other representations that are compatible with the DNS RCODE will be
-/// supported.
-/// - <MAC> and <OtherData> is a BASE-64 encoded string that does
-/// not contain space characters.
-/// When <MACSize> and <OtherLen> is 0, <MAC> and
-/// <OtherData> must not appear in \c tsig_str, respectively.
-/// - The decoded data of <MAC> is <MACSize> bytes of binary
-/// stream.
-/// - The decoded data of <OtherData> is <OtherLen> bytes of
-/// binary stream.
+///
+/// Note that, since the Algorithm Name field is defined to be "in domain name
+/// syntax", but it is not actually a domain name, it does not have to be
+/// fully qualified.
+///
+/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic
+/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY", and
+/// "BADTIME" are supported (case sensitive). In future versions other
+/// representations that are compatible with the DNS RCODE may be supported.
+///
+/// The MAC and Other Data fields are base-64 encoded strings that do not
+/// contain space characters.
+/// If the MAC Size field is 0, the MAC field must not appear in \c tsig_str.
+/// If the Other Len field is 0, the Other Data field must not appear in
+/// \c tsig_str.
+/// The decoded data of the MAC field is MAC Size bytes of binary stream.
+/// The decoded data of the Other Data field is Other Len bytes of binary
+/// stream.
///
/// An example of valid string is:
/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
-/// In this example <OtherData> is missing because <OtherLen> is 0.
+/// In this example Other Data is missing because Other Len is 0.
///
/// Note that RFC2845 does not define the standard presentation format
/// of %TSIG RR, so the above syntax is implementation specific.
/// This is, however, compatible with the format acceptable to BIND 9's
/// RDATA parser.
///
-/// <b>Exceptions</b>
+/// \throw Others Exception from the Name constructors.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+/// \throw BadValue if MAC or Other Data is not validly encoded in base-64.
///
-/// If <Alg> is not a valid domain name, a corresponding exception from
-/// the \c Name class will be thrown;
-/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an
-/// exception of class \c isc::BadValue will be thrown;
-/// if %any of the other bullet points above is not met, an exception of
-/// class \c InvalidRdataText will be thrown.
-/// This constructor internally involves resource allocation, and if it fails
-/// a corresponding standard exception will be thrown.
+/// \param tsig_str A string containing the RDATA to be created
TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
- istringstream iss(tsig_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the TSIGImpl that constructFromLexer() returns.
+ std::auto_ptr<TSIGImpl> impl_ptr(NULL);
try {
- const Name algorithm(getToken(iss));
- const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss));
- const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss));
- const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss));
-
- const string mac_txt = (macsize > 0) ? getToken(iss) : "";
- vector<uint8_t> mac;
- decodeBase64(mac_txt, mac);
- if (mac.size() != macsize) {
- isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
- }
-
- const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss));
-
- const string error_txt = getToken(iss);
- int32_t error = 0;
- // XXX: In the initial implementation we hardcode the mnemonics.
- // We'll soon generalize this.
- if (error_txt == "BADSIG") {
- error = 16;
- } else if (error_txt == "BADKEY") {
- error = 17;
- } else if (error_txt == "BADTIME") {
- error = 18;
- } else {
- error = tokenToNum<int32_t, 16>(error_txt);
- }
+ std::istringstream ss(tsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
- const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss));
- const string otherdata_txt = (otherlen > 0) ? getToken(iss) : "";
- vector<uint8_t> other_data;
- decodeBase64(otherdata_txt, other_data);
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
- tsig_str);
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for TSIG: " << tsig_str);
}
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct TSIG from '" << tsig_str << "': "
+ << ex.what());
+ }
- impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
- error, other_data);
+ impl_ = impl_ptr.release();
+}
- } catch (const StringTokenError& ste) {
- isc_throw(InvalidRdataText, "Invalid TSIG text: " << ste.what() <<
- ": " << tsig_str);
- }
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an TSIG RDATA.
+///
+/// See \c TSIG::TSIG(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TSIG::TSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -183,7 +270,9 @@ TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
/// But this constructor does not use this parameter; if necessary, the caller
/// must check consistency between the length parameter and the actual
/// RDATA length.
-TSIG::TSIG(InputBuffer& buffer, size_t) : impl_(NULL) {
+TSIG::TSIG(InputBuffer& buffer, size_t) :
+ impl_(NULL)
+{
Name algorithm(buffer);
uint8_t time_signed_buf[6];
@@ -298,7 +387,7 @@ TSIG::toText() const {
// toWire().
template <typename Output>
void
-TSIG::TSIGImpl::toWireCommon(Output& output) const {
+TSIGImpl::toWireCommon(Output& output) const {
output.writeUint16(time_signed_ >> 32);
output.writeUint32(time_signed_ & 0xffffffff);
output.writeUint16(fudge_);
diff --git a/src/lib/dns/rdata/any_255/tsig_250.h b/src/lib/dns/rdata/any_255/tsig_250.h
index ff24925..62f599a 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.h
+++ b/src/lib/dns/rdata/any_255/tsig_250.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,14 +18,9 @@
#include <string>
+#include <dns/name.h>
#include <dns/rdata.h>
-namespace isc {
-namespace dns {
-class Name;
-}
-}
-
// BEGIN_ISC_NAMESPACE
// BEGIN_COMMON_DECLARATIONS
@@ -33,6 +28,8 @@ class Name;
// BEGIN_RDATA_NAMESPACE
+struct TSIGImpl;
+
/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in
/// RFC2845.
///
@@ -145,7 +142,8 @@ public:
/// This method never throws an exception.
const void* getOtherData() const;
private:
- struct TSIGImpl;
+ TSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
TSIGImpl* impl_;
};
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
index 3d13a9e..5e2852f 100644
--- a/src/lib/dns/rdata/ch_3/a_1.cc
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,12 @@ A::A(const std::string&) {
// TBD
}
+A::A(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ // TBD
+}
+
A::A(InputBuffer&, size_t) {
// TBD
}
diff --git a/src/lib/dns/rdata/generic/minfo_14.cc b/src/lib/dns/rdata/generic/minfo_14.cc
index aa5272c..58d6f3c 100644
--- a/src/lib/dns/rdata/generic/minfo_14.cc
+++ b/src/lib/dns/rdata/generic/minfo_14.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,12 @@
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using namespace isc::dns;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -39,12 +41,10 @@ using namespace isc::util;
/// An example of valid string is:
/// \code "rmail.example.com. email.example.com." \endcode
///
-/// <b>Exceptions</b>
-///
-/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
/// incorrect.
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the string is invalid.
MINFO::MINFO(const std::string& minfo_str) :
// We cannot construct both names in the initialization list due to the
@@ -52,21 +52,44 @@ MINFO::MINFO(const std::string& minfo_str) :
// name and replace them later.
rmailbox_(Name::ROOT_NAME()), emailbox_(Name::ROOT_NAME())
{
- istringstream iss(minfo_str);
- string rmailbox_str, emailbox_str;
- iss >> rmailbox_str >> emailbox_str;
-
- // Validation: A valid MINFO RR must have exactly two fields.
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid MINFO text: " << minfo_str);
- }
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid MINFO text (redundant field): "
- << minfo_str);
+ try {
+ std::istringstream ss(minfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ rmailbox_ = createNameFromLexer(lexer, NULL);
+ emailbox_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MINFO: "
+ << minfo_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MINFO from '" <<
+ minfo_str << "': " << ex.what());
}
+}
- rmailbox_ = Name(rmailbox_str);
- emailbox_ = Name(emailbox_str);
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MINFO RDATA. The RMAILBOX and EMAILBOX fields can be non-absolute
+/// if \c origin is non-NULL, in which case \c origin is used to make them
+/// absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and constructors if construction of
+/// textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+MINFO::MINFO(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ rmailbox_(createNameFromLexer(lexer, origin)),
+ emailbox_(createNameFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -75,8 +98,8 @@ MINFO::MINFO(const std::string& minfo_str) :
/// length) for parsing.
/// If necessary, the caller will check consistency.
///
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the wire is invalid.
MINFO::MINFO(InputBuffer& buffer, size_t) :
rmailbox_(buffer), emailbox_(buffer)
@@ -84,7 +107,7 @@ MINFO::MINFO(InputBuffer& buffer, size_t) :
/// \brief Copy constructor.
///
-/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// \throw std::bad_alloc Memory allocation fails in copying internal
/// member variables (this should be very rare).
MINFO::MINFO(const MINFO& other) :
Rdata(), rmailbox_(other.rmailbox_), emailbox_(other.emailbox_)
@@ -95,7 +118,7 @@ MINFO::MINFO(const MINFO& other) :
/// The output of this method is formatted as described in the "from string"
/// constructor (\c MINFO(const std::string&))).
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \return A \c string object that represents the \c MINFO object.
std::string
@@ -105,7 +128,7 @@ MINFO::toText() const {
/// \brief Render the \c MINFO in the wire format without name compression.
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param buffer An output buffer to store the wire data.
void
@@ -128,7 +151,7 @@ MINFO::operator=(const MINFO& source) {
/// As specified in RFC3597, TYPE MINFO is "well-known", the rmailbox and
/// emailbox fields (domain names) will be compressed.
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param renderer DNS message rendering context that encapsulates the
/// output buffer and name compression information.
diff --git a/src/lib/dns/rdata/generic/minfo_14.h b/src/lib/dns/rdata/generic/minfo_14.h
index f3ee1d0..ce4586f 100644
--- a/src/lib/dns/rdata/generic/minfo_14.h
+++ b/src/lib/dns/rdata/generic/minfo_14.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -45,7 +45,7 @@ public:
/// \brief Return the value of the rmailbox field.
///
- /// \exception std::bad_alloc If resource allocation for the returned
+ /// \throw std::bad_alloc If resource allocation for the returned
/// \c Name fails.
///
/// \note
@@ -64,7 +64,7 @@ public:
/// \brief Return the value of the emailbox field.
///
- /// \exception std::bad_alloc If resource allocation for the returned
+ /// \throw std::bad_alloc If resource allocation for the returned
/// \c Name fails.
Name getEmailbox() const { return (emailbox_); }
diff --git a/src/lib/dns/rdata/generic/rp_17.cc b/src/lib/dns/rdata/generic/rp_17.cc
index 781b55d..d3d86fd 100644
--- a/src/lib/dns/rdata/generic/rp_17.cc
+++ b/src/lib/dns/rdata/generic/rp_17.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,12 @@
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using namespace isc::dns;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -36,32 +38,54 @@ using namespace isc::util;
/// \endcode
/// where both fields must represent a valid domain name.
///
-/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
/// incorrect.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// given name is invalid.
-/// \exception std::bad_alloc Memory allocation for names fails.
RP::RP(const std::string& rp_str) :
// We cannot construct both names in the initialization list due to the
// necessary text processing, so we have to initialize them with a dummy
// name and replace them later.
mailbox_(Name::ROOT_NAME()), text_(Name::ROOT_NAME())
{
- istringstream iss(rp_str);
- string mailbox_str, text_str;
- iss >> mailbox_str >> text_str;
-
- // Validation: A valid RP RR must have exactly two fields.
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid RP text: " << rp_str);
- }
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid RP text (redundant field): "
- << rp_str);
+ try {
+ std::istringstream ss(rp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mailbox_ = createNameFromLexer(lexer, NULL);
+ text_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RP: "
+ << rp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RP from '" <<
+ rp_str << "': " << ex.what());
}
+}
- mailbox_ = Name(mailbox_str);
- text_ = Name(text_str);
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RP RDATA. The MAILBOX and TEXT fields can be non-absolute if \c
+/// origin is non-NULL, in which case \c origin is used to make them absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and constructors if construction of
+/// textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+RP::RP(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mailbox_(createNameFromLexer(lexer, origin)),
+ text_(createNameFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -70,15 +94,15 @@ RP::RP(const std::string& rp_str) :
/// length) for parsing.
/// If necessary, the caller will check consistency.
///
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the wire is invalid.
RP::RP(InputBuffer& buffer, size_t) : mailbox_(buffer), text_(buffer) {
}
/// \brief Copy constructor.
///
-/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// \throw std::bad_alloc Memory allocation fails in copying internal
/// member variables (this should be very rare).
RP::RP(const RP& other) :
Rdata(), mailbox_(other.mailbox_), text_(other.text_)
@@ -89,7 +113,7 @@ RP::RP(const RP& other) :
/// The output of this method is formatted as described in the "from string"
/// constructor (\c RP(const std::string&))).
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \return A \c string object that represents the \c RP object.
std::string
@@ -97,20 +121,36 @@ RP::toText() const {
return (mailbox_.toText() + " " + text_.toText());
}
+/// \brief Render the \c RP in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
void
RP::toWire(OutputBuffer& buffer) const {
mailbox_.toWire(buffer);
text_.toWire(buffer);
}
+/// \brief Render the \c RP in the wire format with taking into account
+/// compression.
+///
+// Type RP is not "well-known", and name compression must be disabled
+// per RFC3597.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
void
RP::toWire(AbstractMessageRenderer& renderer) const {
- // Type RP is not "well-known", and name compression must be disabled
- // per RFC3597.
renderer.writeName(mailbox_, false);
renderer.writeName(text_, false);
}
+/// \brief Compare two instances of \c RP RDATA.
+///
+/// See documentation in \c Rdata.
int
RP::compare(const Rdata& other) const {
const RP& other_rp = dynamic_cast<const RP&>(other);
diff --git a/src/lib/dns/rdata/generic/rp_17.h b/src/lib/dns/rdata/generic/rp_17.h
index a90a530..2fb89df 100644
--- a/src/lib/dns/rdata/generic/rp_17.h
+++ b/src/lib/dns/rdata/generic/rp_17.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -49,9 +49,8 @@ public:
/// \brief Return the value of the mailbox field.
///
- /// This method normally does not throw an exception, but if resource
- /// allocation for the returned \c Name object fails, a corresponding
- /// standard exception will be thrown.
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
///
/// \note
/// Unlike the case of some other RDATA classes (such as
@@ -69,9 +68,8 @@ public:
/// \brief Return the value of the text field.
///
- /// This method normally does not throw an exception, but if resource
- /// allocation for the returned \c Name object fails, a corresponding
- /// standard exception will be thrown.
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
Name getText() const { return (text_); }
private:
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index 64b3cc1..c7199f3 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -35,122 +35,239 @@ using namespace isc::util::encode;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 2) {
- isc_throw(InvalidRdataLength, "SSHFP record too short");
+struct SSHFPImpl {
+ // straightforward representation of SSHFP RDATA fields
+ SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
+ vector<uint8_t>& fingerprint) :
+ algorithm_(algorithm),
+ fingerprint_type_(fingerprint_type),
+ fingerprint_(fingerprint)
+ {}
+
+ uint8_t algorithm_;
+ uint8_t fingerprint_type_;
+ const vector<uint8_t> fingerprint_;
+};
+
+// helper function for string and lexer constructors
+SSHFPImpl*
+SSHFP::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 255) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
}
- algorithm_ = buffer.readUint8();
- fingerprint_type_ = buffer.readUint8();
-
- rdata_len -= 2;
- if (rdata_len > 0) {
- fingerprint_.resize(rdata_len);
- buffer.readData(&fingerprint_[0], rdata_len);
+ const uint32_t fingerprint_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fingerprint_type > 255) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
}
-}
-
-SSHFP::SSHFP(const std::string& sshfp_str) {
- std::istringstream iss(sshfp_str);
- std::stringbuf fingerprintbuf;
- uint32_t algorithm, fingerprint_type;
- iss >> algorithm >> fingerprint_type;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SSHFP text");
+ std::string fingerprint_str;
+ std::string fingerprint_substr;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(fingerprint_substr);
+ fingerprint_str.append(fingerprint_substr);
}
+ lexer.ungetToken();
- if (algorithm > 255) {
- isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ vector<uint8_t> fingerprint;
+ // If fingerprint is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (fingerprint_str.size() > 0) {
+ decodeHex(fingerprint_str, fingerprint);
}
- if (fingerprint_type > 255) {
- isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
- }
+ return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SSHFP RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Algorithm and Fingerprint Type fields must be within their valid
+/// ranges, but are not constrained to the values defined in RFC4255.
+///
+/// The Fingerprint field may be absent, but if present it must contain a
+/// valid hex encoding of the fingerprint. For compatibility with BIND 9,
+/// whitespace is allowed in the hex text (RFC4255 is silent on the matter).
+///
+/// \throw InvalidRdataText if any fields are missing, out of their valid
+/// ranges, or incorrect.
+///
+/// \param sshfp_str A string containing the RDATA to be created
+SSHFP::SSHFP(const string& sshfp_str) :
+ impl_(NULL)
+{
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the SSHFPImpl that constructFromLexer() returns.
+ std::auto_ptr<SSHFPImpl> impl_ptr(NULL);
- iss >> &fingerprintbuf;
try {
- decodeHex(fingerprintbuf.str(), fingerprint_);
+ std::istringstream ss(sshfp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SSHFP: "
+ << sshfp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
+ sshfp_str << "': " << ex.what());
} catch (const isc::BadValue& e) {
isc_throw(InvalidRdataText,
"Bad SSHFP fingerprint: " << e.what());
}
- algorithm_ = algorithm;
- fingerprint_type_ = fingerprint_type;
+ impl_ = impl_ptr.release();
}
-SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
- const std::string& fingerprint)
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SSHFP RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect.
+/// \throw BadValue Fingerprint is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+SSHFP::SSHFP(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
{
- algorithm_ = algorithm;
- fingerprint_type_ = fingerprint_type;
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid SSHFP RDATA.
+///
+/// The Algorithm and Fingerprint Type fields are not checked for unknown
+/// values. It is okay for the fingerprint data to be missing (see the
+/// description of the constructor from string).
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "SSHFP record too short");
+ }
+
+ const uint8_t algorithm = buffer.readUint8();
+ const uint8_t fingerprint_type = buffer.readUint8();
+
+ vector<uint8_t> fingerprint;
+ rdata_len -= 2;
+ if (rdata_len > 0) {
+ fingerprint.resize(rdata_len);
+ buffer.readData(&fingerprint[0], rdata_len);
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const string& fingerprint_txt) :
+ impl_(NULL)
+{
+ vector<uint8_t> fingerprint;
try {
- decodeHex(fingerprint, fingerprint_);
+ decodeHex(fingerprint_txt, fingerprint);
} catch (const isc::BadValue& e) {
isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
}
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
}
SSHFP::SSHFP(const SSHFP& other) :
- Rdata(), algorithm_(other.algorithm_),
- fingerprint_type_(other.fingerprint_type_),
- fingerprint_(other.fingerprint_)
+ Rdata(), impl_(new SSHFPImpl(*other.impl_))
{}
+SSHFP&
+SSHFP::operator=(const SSHFP& source) {
+ if (impl_ == source.impl_) {
+ return (*this);
+ }
+
+ SSHFPImpl* newimpl = new SSHFPImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SSHFP::~SSHFP() {
+ delete impl_;
+}
+
void
SSHFP::toWire(OutputBuffer& buffer) const {
- buffer.writeUint8(algorithm_);
- buffer.writeUint8(fingerprint_type_);
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeUint8(impl_->fingerprint_type_);
- if (!fingerprint_.empty()) {
- buffer.writeData(&fingerprint_[0], fingerprint_.size());
+ if (!impl_->fingerprint_.empty()) {
+ buffer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
}
}
void
SSHFP::toWire(AbstractMessageRenderer& renderer) const {
- renderer.writeUint8(algorithm_);
- renderer.writeUint8(fingerprint_type_);
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeUint8(impl_->fingerprint_type_);
- if (!fingerprint_.empty()) {
- renderer.writeData(&fingerprint_[0], fingerprint_.size());
+ if (!impl_->fingerprint_.empty()) {
+ renderer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
}
}
string
SSHFP::toText() const {
- return (lexical_cast<string>(static_cast<int>(algorithm_)) +
- " " + lexical_cast<string>(static_cast<int>(fingerprint_type_)) +
- (fingerprint_.empty() ? "" : " " + encodeHex(fingerprint_)));
+ return (lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->fingerprint_type_)) +
+ (impl_->fingerprint_.empty() ? "" :
+ " " + encodeHex(impl_->fingerprint_)));
}
int
SSHFP::compare(const Rdata& other) const {
const SSHFP& other_sshfp = dynamic_cast<const SSHFP&>(other);
- /* This doesn't really make any sort of sense, but in the name of
- consistency... */
-
- if (algorithm_ < other_sshfp.algorithm_) {
+ if (impl_->algorithm_ < other_sshfp.impl_->algorithm_) {
return (-1);
- } else if (algorithm_ > other_sshfp.algorithm_) {
+ } else if (impl_->algorithm_ > other_sshfp.impl_->algorithm_) {
return (1);
}
- if (fingerprint_type_ < other_sshfp.fingerprint_type_) {
+ if (impl_->fingerprint_type_ < other_sshfp.impl_->fingerprint_type_) {
return (-1);
- } else if (fingerprint_type_ > other_sshfp.fingerprint_type_) {
+ } else if (impl_->fingerprint_type_ >
+ other_sshfp.impl_->fingerprint_type_) {
return (1);
}
- const size_t this_len = fingerprint_.size();
- const size_t other_len = other_sshfp.fingerprint_.size();
+ const size_t this_len = impl_->fingerprint_.size();
+ const size_t other_len = other_sshfp.impl_->fingerprint_.size();
const size_t cmplen = min(this_len, other_len);
if (cmplen > 0) {
- const int cmp = memcmp(&fingerprint_[0], &other_sshfp.fingerprint_[0],
+ const int cmp = memcmp(&impl_->fingerprint_[0],
+ &other_sshfp.impl_->fingerprint_[0],
cmplen);
if (cmp != 0) {
return (cmp);
@@ -168,17 +285,17 @@ SSHFP::compare(const Rdata& other) const {
uint8_t
SSHFP::getAlgorithmNumber() const {
- return (algorithm_);
+ return (impl_->algorithm_);
}
uint8_t
SSHFP::getFingerprintType() const {
- return (fingerprint_type_);
+ return (impl_->fingerprint_type_);
}
size_t
SSHFP::getFingerprintLen() const {
- return (fingerprint_.size());
+ return (impl_->fingerprint_.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
index d9ebea4..28ce0f3 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.h
+++ b/src/lib/dns/rdata/generic/sshfp_44.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -28,12 +28,17 @@
// BEGIN_RDATA_NAMESPACE
+struct SSHFPImpl;
+
class SSHFP : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
- SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint);
+ SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const std::string& fingerprint);
+ SSHFP& operator=(const SSHFP& source);
+ ~SSHFP();
///
/// Specialized methods
@@ -43,11 +48,9 @@ public:
size_t getFingerprintLen() const;
private:
- /// Note: this is a prototype version; we may reconsider
- /// this representation later.
- uint8_t algorithm_;
- uint8_t fingerprint_type_;
- std::vector<uint8_t> fingerprint_;
+ SSHFPImpl* constructFromLexer(MasterLexer& lexer);
+
+ SSHFPImpl* impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
index 3d13a9e..5e2852f 100644
--- a/src/lib/dns/rdata/hs_4/a_1.cc
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,12 @@ A::A(const std::string&) {
// TBD
}
+A::A(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ // TBD
+}
+
A::A(InputBuffer&, size_t) {
// TBD
}
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 5960759..ae735bc 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -41,35 +41,6 @@ using namespace isc::dns::rdata;
namespace isc {
namespace dns {
-namespace rdata {
-
-RdataPtr
-AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
- MasterLoader::Options,
- MasterLoaderCallbacks&) const
-{
- std::string s;
-
- while (true) {
- const MasterToken& token = lexer.getNextToken();
- if ((token.getType() == MasterToken::END_OF_FILE) ||
- (token.getType() == MasterToken::END_OF_LINE)) {
- lexer.ungetToken(); // let the upper layer handle the end-of token
- break;
- }
-
- if (!s.empty()) {
- s += " ";
- }
-
- s += token.getString();
- }
-
- return (create(s));
-}
-
-} // end of namespace isc::dns::rdata
-
namespace {
///
/// The following function and class are a helper to define case-insensitive
@@ -190,10 +161,8 @@ typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
template <typename T>
-class OldRdataFactory : public AbstractRdataFactory {
+class RdataFactory : public AbstractRdataFactory {
public:
- using AbstractRdataFactory::create;
-
virtual RdataPtr create(const string& rdata_str) const
{
return (RdataPtr(new T(rdata_str)));
@@ -208,16 +177,11 @@ public:
{
return (RdataPtr(new T(dynamic_cast<const T&>(source))));
}
-};
-
-template <typename T>
-class RdataFactory : public OldRdataFactory<T> {
-public:
- using OldRdataFactory<T>::create;
virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
- MasterLoaderCallbacks& callbacks) const {
+ MasterLoaderCallbacks& callbacks) const
+ {
return (RdataPtr(new T(lexer, origin, options, callbacks)));
}
};
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index bf86436..1d59e01 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -55,11 +55,11 @@ namespace rdata {
/// \brief The \c AbstractRdataFactory class is an abstract base class to
/// encapsulate a set of Rdata factory methods in a polymorphic way.
///
-/// An external developers who want to introduce a new or experimental RR type
-/// are expected to define a corresponding derived class of \c
+/// An external developer who wants to introduce a new or experimental RR type
+/// is expected to define a corresponding derived class of \c
/// AbstractRdataFactory and register it via \c RRParamRegistry.
///
-/// For other users of this API normally do not have to care about this class
+/// Other users of this API normally do not have to care about this class
/// or its derived classes; this class is generally intended to be used
/// as an internal utility of the API implementation.
class AbstractRdataFactory {
@@ -125,16 +125,9 @@ public:
/// of a specific RR type and class for \c RRParamRegistry::createRdata()
/// that uses \c MasterLexer. See its description for the expected
/// behavior and meaning of the parameters.
- ///
- /// \note Right now this is not defined as a pure virtual method and
- /// provides the default implementation. This is an intermediate
- /// workaround until we implement the underlying constructor for all
- /// supported \c Rdata classes; once it's completed the workaround
- /// default implementation should be removed and this method should become
- /// pure virtual.
virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
- MasterLoaderCallbacks& callbacks) const;
+ MasterLoaderCallbacks& callbacks) const = 0;
//@}
};
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 2f717fe..3ce6a6c 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -1,6 +1,6 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
-// Permission to use, copy, modify, and/or distribute this software for generic
+// 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.
//
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <string>
+
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
@@ -27,22 +29,62 @@
using isc::UnitTestUtil;
using namespace std;
-using namespace isc::dns;
using namespace isc::util;
+using namespace isc::dns;
using namespace isc::dns::rdata;
-// minfo text
-const char* const minfo_txt = "rmailbox.example.com. emailbox.example.com.";
-const char* const minfo_txt2 = "root.example.com. emailbox.example.com.";
-const char* const too_long_label = "01234567890123456789012345678901234567"
- "89012345678901234567890123";
-
namespace {
class Rdata_MINFO_Test : public RdataTest {
-public:
+protected:
Rdata_MINFO_Test():
- rdata_minfo(string(minfo_txt)), rdata_minfo2(string(minfo_txt2)) {}
-
+ minfo_txt("rmailbox.example.com. emailbox.example.com."),
+ minfo_txt2("root.example.com. emailbox.example.com."),
+ too_long_label("01234567890123456789012345678901234567"
+ "89012345678901234567890123."),
+ rdata_minfo(minfo_txt),
+ rdata_minfo2(minfo_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::MINFO, isc::Exception, isc::Exception>(
+ rdata_str, rdata_minfo, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::MINFO, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_minfo, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::MINFO, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_minfo, true, false, origin);
+ }
+
+ const string minfo_txt;
+ const string minfo_txt2;
+ const string too_long_label;
const generic::MINFO rdata_minfo;
const generic::MINFO rdata_minfo2;
};
@@ -54,24 +96,35 @@ TEST_F(Rdata_MINFO_Test, createFromText) {
EXPECT_EQ(Name("root.example.com."), rdata_minfo2.getRmailbox());
EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo2.getEmailbox());
+
+ checkFromText_None(minfo_txt);
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("rmailbox emailbox", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("rmailbox.example.com. emailbox.example.com. "
+ "extra.example.com.");
}
TEST_F(Rdata_MINFO_Test, badText) {
- // incomplete text
- EXPECT_THROW(generic::MINFO("root.example.com."),
- InvalidRdataText);
- // number of fields (must be 2) is incorrect
- EXPECT_THROW(generic::MINFO("root.example.com emailbox.example.com. "
- "example.com."),
- InvalidRdataText);
- // bad rmailbox name
- EXPECT_THROW(generic::MINFO("root.example.com. emailbox.example.com." +
- string(too_long_label)),
- TooLongLabel);
- // bad emailbox name
- EXPECT_THROW(generic::MINFO("root.example.com." +
- string(too_long_label) + " emailbox.example.com."),
- TooLongLabel);
+ // too long names
+ checkFromText_TooLongLabel("root.example.com." + too_long_label +
+ " emailbox.example.com.");
+ checkFromText_TooLongLabel("root.example.com. emailbox.example.com." +
+ too_long_label);
+
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. emailbox.example.com.");
+ checkFromText_EmptyLabel("root.example.com. emailbox..example.com.");
+
+ // missing name
+ checkFromText_LexerError("root.example.com.");
+
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com emailbox.example.com.");
+ checkFromText_MissingOrigin("root.example.com. emailbox.example.com");
}
TEST_F(Rdata_MINFO_Test, createFromWire) {
@@ -103,12 +156,6 @@ TEST_F(Rdata_MINFO_Test, createFromWire) {
DNSMessageFORMERR);
}
-TEST_F(Rdata_MINFO_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_minfo.compare(
- *test::createRdataUsingLexer(RRType::MINFO(), RRClass::IN(),
- minfo_txt)));
-}
-
TEST_F(Rdata_MINFO_Test, assignment) {
generic::MINFO copy((string(minfo_txt2)));
copy = rdata_minfo;
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 5508d9c..38bec04 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -41,6 +41,38 @@ protected:
obuffer(0)
{}
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rp, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::RP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rp, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::RP, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RP, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::RP, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_rp, true, false, origin);
+ }
+
const Name mailbox_name, text_name;
const generic::RP rdata_rp; // commonly used test RDATA
OutputBuffer obuffer;
@@ -52,17 +84,28 @@ TEST_F(Rdata_RP_Test, createFromText) {
EXPECT_EQ(mailbox_name, rdata_rp.getMailbox());
EXPECT_EQ(text_name, rdata_rp.getText());
- // Invalid textual input cases follow:
- // names are invalid
- EXPECT_THROW(generic::RP("bad..name. rp-text.example.com"), EmptyLabel);
- EXPECT_THROW(generic::RP("mailbox.example.com. bad..name"), EmptyLabel);
+ checkFromText_None("root.example.com. rp-text.example.com.");
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("root rp-text", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("root.example.com. rp-text.example.com. "
+ "extra.example.com.");
+}
+
+TEST_F(Rdata_RP_Test, badText) {
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. rp-text.example.com.");
+ checkFromText_EmptyLabel("root.example.com. rp-text..example.com.");
// missing field
- EXPECT_THROW(generic::RP("mailbox.example.com."), InvalidRdataText);
+ checkFromText_LexerError("root.example.com.");
- // redundant field
- EXPECT_THROW(generic::RP("mailbox.example.com. rp-text.example.com. "
- "redundant.example."), InvalidRdataText);
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com rp-text.example.com.");
+ checkFromText_MissingOrigin("root.example.com. rp-text.example.com");
}
TEST_F(Rdata_RP_Test, createFromWire) {
@@ -106,17 +149,6 @@ TEST_F(Rdata_RP_Test, createFromParams) {
EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
}
-TEST_F(Rdata_RP_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_rp.compare(
- *test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
- "root.example.com. "
- "rp-text.example.com.")));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
- "mailbox.example.com."));
-}
-
TEST_F(Rdata_RP_Test, toWireBuffer) {
// construct expected data
UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index 6c13ad9..b85dfa5 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,17 +30,50 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
class Rdata_SSHFP_Test : public RdataTest {
- // there's nothing to specialize
+protected:
+ Rdata_SSHFP_Test() :
+ sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890"),
+ rdata_sshfp(sshfp_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::SSHFP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_sshfp, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::SSHFP, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::SSHFP, InvalidRdataText, BadValue>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_sshfp, true, false);
+ }
+
+ const string sshfp_txt;
+ const generic::SSHFP rdata_sshfp;
};
-const string sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890");
-const generic::SSHFP rdata_sshfp(2, 1, "123456789abcdef67890123456789abcdef67890");
const uint8_t rdata_sshfp_wiredata[] = {
// algorithm
0x02,
@@ -56,22 +89,23 @@ const uint8_t rdata_sshfp_wiredata[] = {
TEST_F(Rdata_SSHFP_Test, createFromText) {
// Basic test
- const generic::SSHFP rdata_sshfp2(sshfp_txt);
- EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+ checkFromText_None(sshfp_txt);
// With different spacing
- const generic::SSHFP rdata_sshfp3("2 1 123456789abcdef67890123456789abcdef67890");
- EXPECT_EQ(0, rdata_sshfp3.compare(rdata_sshfp));
+ checkFromText_None("2 1 123456789abcdef67890123456789abcdef67890");
// Combination of lowercase and uppercase
- const generic::SSHFP rdata_sshfp4("2 1 123456789ABCDEF67890123456789abcdef67890");
- EXPECT_EQ(0, rdata_sshfp4.compare(rdata_sshfp));
-}
+ checkFromText_None("2 1 123456789ABCDEF67890123456789abcdef67890");
-TEST_F(Rdata_SSHFP_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_sshfp.compare(
- *test::createRdataUsingLexer(RRType::SSHFP(), RRClass::IN(),
- "2 1 123456789abcdef67890123456789abcdef67890")));
+ // spacing in the fingerprint field
+ checkFromText_None("2 1 123456789abcdef67890 123456789abcdef67890");
+
+ // multi-line fingerprint field
+ checkFromText_None("2 1 ( 123456789abcdef67890\n 123456789abcdef67890 )");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(sshfp_txt + "\n" + sshfp_txt);
}
TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
@@ -101,13 +135,30 @@ TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
}
TEST_F(Rdata_SSHFP_Test, badText) {
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("1"), InvalidRdataText);
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("BUCKLE MY SHOES"), InvalidRdataText);
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 foo bar"), InvalidRdataText);
+ checkFromText_LexerError("1");
+ checkFromText_LexerError("ONE 2 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("1 TWO 123456789abcdef67890123456789abcdef67890");
+ checkFromText_BadValue("1 2 BUCKLEMYSHOE");
+ checkFromText_BadValue(sshfp_txt + " extra text");
+
+ // yes, these are redundant to the last test cases in algorithmTypes
+ checkFromText_InvalidText(
+ "2345 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText(
+ "2 1234 123456789abcdef67890123456789abcdef67890");
+
+ // negative values are trapped in the lexer rather than the constructor
+ checkFromText_LexerError("-2 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("2 -1 123456789abcdef67890123456789abcdef67890");
}
-TEST_F(Rdata_SSHFP_Test, copy) {
- const generic::SSHFP rdata_sshfp2(rdata_sshfp);
+TEST_F(Rdata_SSHFP_Test, copyAndAssign) {
+ // Copy construct
+ generic::SSHFP rdata_sshfp2(rdata_sshfp);
+ EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_sshfp2 = rdata_sshfp;
EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
}
@@ -160,6 +211,12 @@ TEST_F(Rdata_SSHFP_Test, createFromWire) {
InvalidBufferPosition);
}
+TEST_F(Rdata_SSHFP_Test, createFromParams) {
+ const generic::SSHFP rdata_sshfp2(
+ 2, 1, "123456789abcdef67890123456789abcdef67890");
+ EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+}
+
TEST_F(Rdata_SSHFP_Test, toText) {
EXPECT_TRUE(boost::iequals(sshfp_txt, rdata_sshfp.toText()));
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index df35842..d351b40 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
+#include <dns/tsigerror.h>
#include <gtest/gtest.h>
@@ -31,34 +32,84 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
+
class Rdata_TSIG_Test : public RdataTest {
protected:
- vector<uint8_t> expect_data;
-};
+ Rdata_TSIG_Test() :
+ // no MAC or Other Data
+ valid_text1("hmac-md5.sig-alg.reg.int. 1286779327 300 "
+ "0 16020 BADKEY 0"),
+ // MAC but no Other Data
+ valid_text2("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 0"),
+ // MAC and Other Data
+ valid_text3("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE"),
+ // MAC and Other Data (with Error that doesn't expect Other Data)
+ valid_text4("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE"),
+ // numeric error code
+ valid_text5("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 2845 0"),
+ rdata_tsig(valid_text1)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<any::TSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_tsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<any::TSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_tsig, true, true);
+ }
-const char* const valid_text1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 "
- "0 16020 BADKEY 0";
-const char* const valid_text2 = "hmac-sha256. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADSIG 0";
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<any::TSIG, BadValue, BadValue>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <any::TSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_tsig, true, true);
+ }
-const char* const valid_text3 = "hmac-sha1. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE";
-const char* const valid_text4 = "hmac-sha1. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE";
-const char* const valid_text5 = "hmac-sha256. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 2845 0"; // using numeric error code
-const char* const too_long_label = "012345678901234567890123456789"
- "0123456789012345678901234567890123";
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<any::TSIG, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
-// commonly used test RDATA
-const any::TSIG rdata_tsig((string(valid_text1)));
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<any::TSIG, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
-TEST_F(Rdata_TSIG_Test, createFromText) {
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <any::TSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_tsig, true, false);
+ }
+
+ template <typename Output>
+ void toWireCommonChecks(Output& output) const;
+
+ const string valid_text1;
+ const string valid_text2;
+ const string valid_text3;
+ const string valid_text4;
+ const string valid_text5;
+ vector<uint8_t> expect_data;
+ const any::TSIG rdata_tsig; // commonly used test RDATA
+};
+
+TEST_F(Rdata_TSIG_Test, fromText) {
// normal case. it also tests getter methods.
EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm());
EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned());
@@ -66,59 +117,80 @@ TEST_F(Rdata_TSIG_Test, createFromText) {
EXPECT_EQ(0, rdata_tsig.getMACSize());
EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getMAC());
EXPECT_EQ(16020, rdata_tsig.getOriginalID());
- EXPECT_EQ(17, rdata_tsig.getError()); // TODO: use constant
+ EXPECT_EQ(TSIGError::BAD_KEY_CODE, rdata_tsig.getError());
EXPECT_EQ(0, rdata_tsig.getOtherLen());
EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getOtherData());
- any::TSIG tsig2((string(valid_text2)));
+ any::TSIG tsig2(valid_text2);
EXPECT_EQ(12, tsig2.getMACSize());
- EXPECT_EQ(16, tsig2.getError()); // TODO: use constant
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig2.getError());
- any::TSIG tsig3((string(valid_text3)));
+ any::TSIG tsig3(valid_text3);
EXPECT_EQ(6, tsig3.getOtherLen());
// The other data is unusual, but we don't reject it.
- EXPECT_NO_THROW(any::TSIG(string(valid_text4)));
+ EXPECT_NO_THROW(any::TSIG tsig4(valid_text4));
// numeric representation of TSIG error
- any::TSIG tsig5((string(valid_text5)));
+ any::TSIG tsig5(valid_text5);
EXPECT_EQ(2845, tsig5.getError());
- //
- // invalid cases
- //
- // there's a garbage parameter at the end
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY 0 0"), InvalidRdataText);
- // input is too short
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY"), InvalidRdataText);
+ // not fully qualified algorithm name
+ any::TSIG tsig1("hmac-md5.sig-alg.reg.int 1286779327 300 "
+ "0 16020 BADKEY 0");
+ EXPECT_EQ(0, tsig1.compare(rdata_tsig));
+
+ // multi-line rdata
+ checkFromText_None("hmac-md5.sig-alg.reg.int. ( 1286779327 300 \n"
+ "0 16020 BADKEY 0 )");
+};
+
+TEST_F(Rdata_TSIG_Test, badText) {
+ // too many fields
+ checkFromText_BadString(valid_text1 + " 0 0");
+ // not enough fields
+ checkFromText_LexerError("foo 0 0 0 0 BADKEY");
// bad domain name
- EXPECT_THROW(any::TSIG(string(too_long_label) + "0 0 0 0 BADKEY 0"),
- TooLongLabel);
+ checkFromText_TooLongLabel(
+ "0123456789012345678901234567890123456789012345678901234567890123"
+ " 0 0 0 0 BADKEY 0");
+ checkFromText_EmptyLabel("foo..bar 0 0 0 0 BADKEY");
// time is too large (2814...6 is 2^48)
- EXPECT_THROW(any::TSIG("foo 281474976710656 0 0 0 BADKEY 0"),
- InvalidRdataText);
+ checkFromText_InvalidText("foo 281474976710656 0 0 0 BADKEY 0");
// invalid time (negative)
- EXPECT_THROW(any::TSIG("foo -1 0 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo -1 0 0 0 BADKEY 0");
+ // invalid time (not a number)
+ checkFromText_InvalidText("foo TIME 0 0 0 BADKEY 0");
// fudge is too large
- EXPECT_THROW(any::TSIG("foo 0 65536 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 65536 0 0 BADKEY 0");
// invalid fudge (negative)
- EXPECT_THROW(any::TSIG("foo 0 -1 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_LexerError("foo 0 -1 0 0 BADKEY 0");
+ // invalid fudge (not a number)
+ checkFromText_LexerError("foo 0 FUDGE 0 0 BADKEY 0");
// MAC size is too large
- EXPECT_THROW(any::TSIG("foo 0 0 65536 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 65536 0 BADKEY 0");
+ // invalide MAC size (negative)
+ checkFromText_LexerError("foo 0 0 -1 0 BADKEY 0");
+ // invalid MAC size (not a number)
+ checkFromText_LexerError("foo 0 0 MACSIZE 0 BADKEY 0");
// MAC size and MAC mismatch
- EXPECT_THROW(any::TSIG("foo 0 0 9 FAKE 0 BADKEY 0"), InvalidRdataText);
- EXPECT_THROW(any::TSIG("foo 0 0 0 FAKE 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 9 FAKE 0 BADKEY 0");
// MAC is bad base64
- EXPECT_THROW(any::TSIG("foo 0 0 3 FAK= 0 BADKEY 0"), isc::BadValue);
+ checkFromText_BadValue("foo 0 0 3 FAK= 0 BADKEY 0");
// Unknown error code
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 TEST 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 TEST 0");
// Numeric error code is too large
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 65536 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 65536 0");
+ // Numeric error code is negative
+ checkFromText_InvalidText("foo 0 0 0 0 -1 0");
// Other len is too large
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 65536 FAKE"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 65536 FAKE");
+ // Other len is negative
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR -1 FAKE");
+ // invalid Other len
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR LEN FAKE");
// Other len and data mismatch
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 9 FAKE"), InvalidRdataText);
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 0 FAKE"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 9 FAKE");
}
void
@@ -221,12 +293,12 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
- EXPECT_EQ(0, any::TSIG((string(valid_text2))).compare(
+ EXPECT_EQ(0, any::TSIG(valid_text2).compare(
any::TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
fake_data, 16020, 16, 0, NULL)));
const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
- EXPECT_EQ(0, any::TSIG((string(valid_text3))).compare(
+ EXPECT_EQ(0, any::TSIG(valid_text3).compare(
any::TSIG(Name("hmac-sha1"), 1286779327, 300, 12,
fake_data, 16020, 18, 6, fake_data2)));
@@ -247,24 +319,14 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
isc::InvalidParameter);
}
-TEST_F(Rdata_TSIG_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_tsig.compare(
- *test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
- valid_text1)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
- "foo 0 0 0 0 BADKEY 0 0"));
-}
-
TEST_F(Rdata_TSIG_Test, assignment) {
- any::TSIG copy((string(valid_text2)));
+ any::TSIG copy(valid_text2);
copy = rdata_tsig;
EXPECT_EQ(0, copy.compare(rdata_tsig));
// Check if the copied data is valid even after the original is deleted
any::TSIG* copy2 = new any::TSIG(rdata_tsig);
- any::TSIG copy3((string(valid_text2)));
+ any::TSIG copy3(valid_text2);
copy3 = *copy2;
delete copy2;
EXPECT_EQ(0, copy3.compare(rdata_tsig));
@@ -276,7 +338,7 @@ TEST_F(Rdata_TSIG_Test, assignment) {
template <typename Output>
void
-toWireCommonChecks(Output& output) {
+Rdata_TSIG_Test::toWireCommonChecks(Output& output) const {
vector<uint8_t> expect_data;
output.clear();
@@ -291,7 +353,7 @@ toWireCommonChecks(Output& output) {
expect_data.clear();
output.clear();
- any::TSIG(string(valid_text2)).toWire(output);
+ any::TSIG(valid_text2).toWire(output);
UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -300,7 +362,7 @@ toWireCommonChecks(Output& output) {
expect_data.clear();
output.clear();
- any::TSIG(string(valid_text3)).toWire(output);
+ any::TSIG(valid_text3).toWire(output);
UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -339,10 +401,10 @@ TEST_F(Rdata_TSIG_Test, toWireRenderer) {
}
TEST_F(Rdata_TSIG_Test, toText) {
- EXPECT_EQ(string(valid_text1), rdata_tsig.toText());
- EXPECT_EQ(string(valid_text2), any::TSIG(string(valid_text2)).toText());
- EXPECT_EQ(string(valid_text3), any::TSIG(string(valid_text3)).toText());
- EXPECT_EQ(string(valid_text5), any::TSIG(string(valid_text5)).toText());
+ EXPECT_EQ(valid_text1, rdata_tsig.toText());
+ EXPECT_EQ(valid_text2, any::TSIG(valid_text2).toText());
+ EXPECT_EQ(valid_text3, any::TSIG(valid_text3).toText());
+ EXPECT_EQ(valid_text5, any::TSIG(valid_text5).toText());
}
TEST_F(Rdata_TSIG_Test, compare) {
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
index 08e0af1..b0d43a8 100644
--- a/src/lib/dns/tests/rrparamregistry_unittest.cc
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -108,13 +108,16 @@ TEST_F(RRParamRegistryTest, addError) {
class TestRdataFactory : public AbstractRdataFactory {
public:
- using AbstractRdataFactory::create;
virtual RdataPtr create(const string& rdata_str) const
{ return (RdataPtr(new in::A(rdata_str))); }
virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const
{ return (RdataPtr(new in::A(buffer, rdata_len))); }
virtual RdataPtr create(const Rdata& source) const
{ return (RdataPtr(new in::A(dynamic_cast<const in::A&>(source)))); }
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const
+ { return (RdataPtr(new in::A(lexer, origin, options, callbacks))); }
};
TEST_F(RRParamRegistryTest, addRemoveFactory) {
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index e801309..206a0ee 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -539,6 +539,42 @@ class ModuleCCSession(ConfigData):
raise RPCError(code, value)
return value
+ def notify(self, notification_group, event_name, params=None):
+ """
+ Send a notification to subscribed users.
+
+ Send a notification message to all users subscribed to the given
+ notification group.
+
+ This method does not block.
+
+ See docs/design/ipc-high.txt for details about notifications
+ and the format of messages sent.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ Params:
+ - notification_group (string): This parameter (indirectly) signifies
+ what users should receive the notification. Only users that
+ subscribed to notifications on the same group receive it.
+ - event_name (string): The name of the event to notify about (for
+ example `new_group_member`).
+ - params: Other parameters that describe the event. This might be, for
+ example, the ID of the new member and the name of the group. This can
+ be any data that can be sent over the isc.cc.Session, but it is
+ common for it to be dict.
+ Returns: Nothing
+ """
+ notification = [event_name]
+ if params is not None:
+ notification.append(params)
+ self._session.group_sendmsg({CC_PAYLOAD_NOTIFICATION: notification},
+ CC_GROUP_NOTIFICATION_PREFIX +
+ notification_group,
+ instance=CC_INSTANCE_WILDCARD,
+ to=CC_TO_WILDCARD,
+ want_answer=False)
+
class UIModuleCCSession(MultiConfigData):
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 2a2d790..3585337 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -350,6 +350,32 @@ class TestModuleCCSession(unittest.TestCase):
self.assertRaises(RPCRecipientMissing, self.rpc_check,
{"result": [-1, "Error"]})
+ def test_notify(self):
+ """
+ Test the sent notification has the right format.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.notify("group", "event", {"param": True})
+ self.assertEqual([
+ ["notifications/group", "*", {"notification": ["event", {
+ "param": True
+ }]}, False]], fake_session.message_queue)
+
+ def test_notify_no_params(self):
+ """
+ Test the sent notification has the right format, this time
+ without passing parameters.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.notify("group", "event")
+ self.assertEqual([
+ ["notifications/group", "*", {"notification": ["event"]},
+ False]
+ ],
+ fake_session.message_queue)
+
def my_config_handler_ok(self, new_config):
return isc.config.ccsession.create_answer(0)
diff --git a/src/lib/util/memory_segment_mapped.cc b/src/lib/util/memory_segment_mapped.cc
index 0d82ffd..8c76e74 100644
--- a/src/lib/util/memory_segment_mapped.cc
+++ b/src/lib/util/memory_segment_mapped.cc
@@ -171,6 +171,7 @@ struct MemorySegmentMapped::Impl {
void growSegment() {
// We first need to unmap it before calling grow().
const size_t prev_size = base_sgmt_->get_size();
+ base_sgmt_->flush();
base_sgmt_.reset();
// Double the segment size. In theory, this process could repeat
@@ -181,16 +182,21 @@ struct MemorySegmentMapped::Impl {
const size_t new_size = prev_size * 2;
assert(new_size > prev_size);
- if (!BaseSegment::grow(filename_.c_str(), new_size - prev_size)) {
- throw std::bad_alloc();
- }
+ const bool grown = BaseSegment::grow(filename_.c_str(),
+ new_size - prev_size);
+ // Remap the file, whether or not grow() succeeded. this should
+ // normally succeed(*), but it's not 100% guaranteed. We abort
+ // if it fails (see the method description in the header file).
+ // (*) Although it's not formally documented, the implementation
+ // of grow() seems to provide strong guarantee, i.e, if it fails
+ // the underlying file can be used with the previous size.
try {
- // Remap the grown file; this should succeed, but it's not 100%
- // guaranteed. If it fails we treat it as if we fail to create
- // the new segment.
base_sgmt_.reset(new BaseSegment(open_only, filename_.c_str()));
- } catch (const boost::interprocess::interprocess_exception& ex) {
+ } catch (...) {
+ abort();
+ }
+ if (!grown) {
throw std::bad_alloc();
}
}
@@ -402,14 +408,20 @@ MemorySegmentMapped::shrinkToFit() {
return;
}
- // First, (unmap and) close the underlying file.
+ // First, unmap the underlying file.
+ impl_->base_sgmt_->flush();
impl_->base_sgmt_.reset();
BaseSegment::shrink_to_fit(impl_->filename_.c_str());
try {
// Remap the shrunk file; this should succeed, but it's not 100%
// guaranteed. If it fails we treat it as if we fail to create
- // the new segment.
+ // the new segment. Note that this is different from the case where
+ // reset() after grow() fails. While the same argument can apply
+ // in theory, it should be less likely that other methods will be
+ // called after shrinkToFit() (and the destructor can still be called
+ // safely), so we give the application an opportunity to handle the
+ // case as gracefully as possible.
impl_->base_sgmt_.reset(
new BaseSegment(open_only, impl_->filename_.c_str()));
} catch (const boost::interprocess::interprocess_exception& ex) {
diff --git a/src/lib/util/memory_segment_mapped.h b/src/lib/util/memory_segment_mapped.h
index 7702d88..f5596f6 100644
--- a/src/lib/util/memory_segment_mapped.h
+++ b/src/lib/util/memory_segment_mapped.h
@@ -150,7 +150,14 @@ public:
/// \brief Allocate/acquire a segment of memory.
///
- /// This version can throw \c MemorySegmentGrown.
+ /// This version can throw \c MemorySegmentGrown. Furthermore, there is
+ /// a very small chance that the object loses its integrity and can't be
+ /// usable in the case where \c MemorySegmentGrown would be thrown.
+ /// In this case, throwing a different exception wouldn't help, because
+ /// an application trying to provide exception safety might then call
+ /// deallocate() or named address APIs on this object, which would simply
+ /// cause a crash. So, while suboptimal, this method just aborts the
+ /// program in this case, indicating there's no hope to shutdown cleanly.
///
/// This method cannot be called if the segment object is created in the
/// read-only mode; in that case MemorySegmentError will be thrown.
diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc
index 475d3e5..287ee38 100644
--- a/src/lib/util/tests/memory_segment_mapped_unittest.cc
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -238,11 +238,12 @@ TEST_F(MemorySegmentMappedTest, allocate) {
TEST_F(MemorySegmentMappedTest, badAllocate) {
// Make the mapped file non-writable; managed_mapped_file::grow() will
- // fail, resulting in std::bad_alloc
+ // fail, resulting in abort.
const int ret = chmod(mapped_file, 0444);
ASSERT_EQ(0, ret);
- EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE * 2), std::bad_alloc);
+ EXPECT_DEATH_IF_SUPPORTED(
+ {segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
}
// XXX: this test can cause too strong side effect (creating a very large
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9f1025b..7c6de04 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1 +1 @@
-SUBDIRS = tools
+SUBDIRS = tools lettuce
diff --git a/tests/lettuce/Makefile.am b/tests/lettuce/Makefile.am
new file mode 100644
index 0000000..82f3e3a
--- /dev/null
+++ b/tests/lettuce/Makefile.am
@@ -0,0 +1 @@
+noinst_SCRIPTS = setup_intree_bind10.sh
diff --git a/tests/lettuce/README b/tests/lettuce/README
index a53c6e7..48c4043 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -6,7 +6,8 @@ these tests are specific for BIND10, but we are keeping in mind that RFC-related
tests could be separated, so that we can test other systems as well.
Prerequisites:
-- BIND 10 must be compiled or installed
+- BIND 10 must be compiled or installed (even when testing in-tree build;
+ see below)
- dig
- lettuce (http://lettuce.it)
@@ -32,7 +33,9 @@ By default it will use the build tree, but you can use an installed version
of bind10 by passing -I as the first argument of run_lettuce.sh
The tool 'dig' must be in the default search path of your environment. If
-you specified -I, so must the main bind10 program, as well as bindctl.
+you specified -I, so must the main BIND 10 programs. And, with or without
+-I, some BIND 10 programs still have to be installed as they are invoked
+from test tools. Those include bindctl and b10-loadzone.
Due to the default way lettuce prints its output, it is advisable to run it
in a terminal that is wide than the default. If you see a lot of lines twice
diff --git a/tests/lettuce/configurations/xfrout_master.conf b/tests/lettuce/configurations/xfrout_master.conf
new file mode 100644
index 0000000..755e698
--- /dev/null
+++ b/tests/lettuce/configurations/xfrout_master.conf
@@ -0,0 +1,41 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrout.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrout.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example.org"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/features/auth_badzone.feature b/tests/lettuce/features/auth_badzone.feature
index 5448b6e..ca805c8 100644
--- a/tests/lettuce/features/auth_badzone.feature
+++ b/tests/lettuce/features/auth_badzone.feature
@@ -2,7 +2,7 @@ Feature: Authoritative DNS server with a bad zone
This feature set is for testing the execution of the b10-auth
component when one zone is broken, whereas others are fine. In this
case, b10-auth should not reject the data source, but reject the bad
- zone only and serve the good zones anyway.
+ zone only (with SERVFAIL) and serve the good zones anyway.
Scenario: Bad zone
Given I have bind10 running with configuration auth/auth_badzone.config
@@ -44,6 +44,6 @@ Feature: Authoritative DNS server with a bad zone
ns2.example.org. 3600 IN A 192.0.2.4
"""
- A query for www.example.com should have rcode REFUSED
- A query for www.example.net should have rcode REFUSED
- A query for www.example.info should have rcode REFUSED
+ A query for www.example.com should have rcode SERVFAIL
+ A query for www.example.net should have rcode SERVFAIL
+ A query for www.example.info should have rcode SERVFAIL
diff --git a/tests/lettuce/features/terrain/loadzone.py b/tests/lettuce/features/terrain/loadzone.py
new file mode 100644
index 0000000..2cf550c
--- /dev/null
+++ b/tests/lettuce/features/terrain/loadzone.py
@@ -0,0 +1,86 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from lettuce import *
+import subprocess
+import tempfile
+
+def run_loadzone(zone, zone_file, db_file):
+ """Run b10-loadzone.
+
+ It currently only works for an SQLite3-based data source, and takes
+ its DB file as a parameter.
+
+ Parameters:
+ zone (str): the zone name
+ zone_file (str): master zone file for the zone
+ db_file (str): SQLite3 DB file to load the zone into
+
+ """
+ sqlite_datasrc_cfg = '{"database_file": "' + db_file + '"}'
+ args = ['b10-loadzone', '-c', sqlite_datasrc_cfg, zone, zone_file]
+ loadzone = subprocess.Popen(args, 1, None, None,
+ subprocess.PIPE, subprocess.PIPE)
+ (stdout, stderr) = loadzone.communicate()
+ result = loadzone.returncode
+ world.last_loadzone_stdout = stdout
+ world.last_loadzone_stderr = stderr
+ assert result == 0, "loadzone exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+
+ at step('load zone (\S+) to DB file (\S+) from (\S+)')
+def load_zone_to_dbfile(step, zone, db_file, zone_file):
+ """Load a zone into a data source from a zone file.
+
+ It currently only works for an SQLite3-based data source. Its
+ DB file name should be specified.
+
+ Step definition:
+ load zone <zone_name> to DB file <db_file> from <zone_file>
+
+ """
+ run_loadzone(zone, zone_file, db_file)
+
+ at step('load (\d+) records for zone (\S+) to DB file (\S+)')
+def load_zone_rr_to_dbfile(step, num_records, zone, db_file):
+ """Load a zone with a specified number of RRs into a data source.
+
+ It currently only works for an SQLite3-based data source. Its
+ DB file name should be specified.
+
+ It creates the content of the zone dynamically so the total number of
+ RRs of the zone is the specified number, including mandatory SOA and NS.
+
+ Step definition:
+ load zone <zone_name> to DB file <db_file> from <zone_file>
+
+ """
+ num_records = int(num_records)
+ assert num_records >= 2, 'zone must have at least 2 RRs: SOA and NS'
+ num_records -= 2
+ with tempfile.NamedTemporaryFile(mode='w', prefix='zone-file',
+ dir='data/', delete=True) as f:
+ filename = f.name
+ f.write('$TTL 3600\n')
+ f.write('$ORIGIN .\n') # so it'll work whether or not zone ends with .
+ f.write(zone + ' IN SOA . . 0 0 0 0 0\n')
+ f.write(zone + ' IN NS 0.' + zone + '\n')
+ count = 0
+ while count < num_records:
+ f.write(str(count) + '.' + zone + ' IN A 192.0.2.1\n')
+ count += 1
+ f.flush()
+ run_loadzone(zone, f.name, db_file)
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index b861442..d82a6e1 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -83,7 +83,9 @@ copylist = [
["data/xfrin-notify.sqlite3.orig",
"data/xfrin-notify.sqlite3"],
["data/ddns/example.org.sqlite3.orig",
- "data/ddns/example.org.sqlite3"]
+ "data/ddns/example.org.sqlite3"],
+ ["data/empty_db.sqlite3",
+ "data/xfrout.sqlite3"]
]
# This is a list of files that, if present, will be removed before a scenario
diff --git a/tests/lettuce/features/terrain/transfer.py b/tests/lettuce/features/terrain/transfer.py
index eb4ddc0..0983de5 100644
--- a/tests/lettuce/features/terrain/transfer.py
+++ b/tests/lettuce/features/terrain/transfer.py
@@ -18,7 +18,8 @@
# and inspect the results.
#
# Like querying.py, it uses dig to do the transfers, and
-# places its output in a result structure
+# places its output in a result structure. It also uses a custom client
+# implementation for less normal operations.
#
# This is done in a different file with different steps than
# querying, because the format of dig's output is
@@ -58,7 +59,16 @@ class TransferResult(object):
if len(line) > 0 and line[0] != ';':
self.records.append(line)
- at step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
+def parse_addr_port(address, port):
+ if address is None:
+ address = "::1" # default address
+ # convert [IPv6_addr] to IPv6_addr:
+ address = re.sub(r"\[(.+)\]", r"\1", address)
+ if port is None:
+ port = 47806 # default port
+ return (address, port)
+
+ at step('An AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
def perform_axfr(step, zone_name, address, port):
"""
Perform an AXFR transfer, and store the result as an instance of
@@ -70,15 +80,60 @@ def perform_axfr(step, zone_name, address, port):
Address defaults to ::1
Port defaults to 47806
"""
- if address is None:
- address = "::1"
- # convert [IPv6_addr] to IPv6_addr:
- address = re.sub(r"\[(.+)\]", r"\1", address)
- if port is None:
- port = 47806
+ (address, port) = parse_addr_port(address, port)
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
world.transfer_result = TransferResult(args)
+ at step('A customized AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?(?: with pose of (\d+) seconds?)?')
+def perform_custom_axfr(step, zone_name, address, port, delay):
+ """Checks AXFR transfer, and store the result in the form of internal
+ CustomTransferResult class, which is compatible with TransferResult.
+
+ Step definition:
+ A customized AXFR transfer of <zone_name> [from <address>:<port>] [with pose of <delay> second]
+
+ If optional delay is specified (not None), it waits for the specified
+ seconds after sending the AXFR query before starting receiving
+ responses. This emulates a slower AXFR client.
+
+ """
+
+ class CustomTransferResult:
+ """Store transfer result only on the number of received answer RRs.
+
+ To be compatible with TransferResult it stores the result in the
+ 'records' attribute, which is a list. But its content is
+ meaningless; its only use is to be used with
+ check_transfer_result_count where its length is of concern.
+
+ """
+ def __init__(self):
+ self.records = []
+
+ # Build arguments and run xfr-client.py. On success, it simply dumps
+ # the number of received answer RRs to stdout.
+ (address, port) = parse_addr_port(address, port)
+ args = ['/bin/sh', 'run_python-tool.sh', 'tools/xfr-client.py',
+ '-s', address, '-p', str(port)]
+ if delay is not None:
+ args.extend(['-d', delay])
+ args.append(zone_name)
+ client = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
+ subprocess.PIPE)
+ (stdout, stderr) = client.communicate()
+ result = client.returncode
+ world.last_client_stdout = stdout
+ world.last_client_stderr = stderr
+ assert result == 0, "xfr-client exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+ num_rrs = int(stdout.strip())
+
+ # Make the result object, storing dummy value (None) for the number of
+ # answer RRs in the records list.
+ world.transfer_result = CustomTransferResult()
+ world.transfer_result.records = [None for _ in range(0, num_rrs)]
+
@step('An IXFR transfer of ([\w.]+) (\d+)(?: from ([^:]+)(?::([0-9]+))?)?(?: over (tcp|udp))?')
def perform_ixfr(step, zone_name, serial, address, port, protocol):
"""
diff --git a/tests/lettuce/features/xfrout_bind10.feature b/tests/lettuce/features/xfrout_bind10.feature
new file mode 100644
index 0000000..7f4e4de
--- /dev/null
+++ b/tests/lettuce/features/xfrout_bind10.feature
@@ -0,0 +1,39 @@
+Feature: Xfrout
+ Tests for Xfrout, specific for BIND 10 behaviour.
+
+ Scenario: normal transfer with a moderate number of RRs
+
+ Load 100 records for zone example.org to DB file data/xfrout.sqlite3
+
+ Given I have bind10 running with configuration xfrout_master.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ # The transferred zone should have the generated 100 RRs plush one
+ # trailing SOA.
+ When I do a customized AXFR transfer of example.org
+ Then transfer result should have 101 rrs
+
+ # Similar to the previous one, but using a much larger zone, and with
+ # a small delay at the client side. It should still succeed.
+ # The specific delay (5 seconds) was chosen for an environment that
+ # revealed a bug which is now fixed to reproduce the issue; shorter delays
+ # didn't trigger the problem. Depending on the OS implementation, machine
+ # speed, etc, the same delay may be too long or too short, but in any case
+ # the test should succeed now.
+ Scenario: transfer a large zone
+
+ Load 50000 records for zone example.org to DB file data/xfrout.sqlite3
+
+ Given I have bind10 running with configuration xfrout_master.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ When I do a customized AXFR transfer of example.org from [::1]:47806 with pose of 5 seconds
+ Then transfer result should have 50001 rrs
diff --git a/tests/lettuce/run_python-tool.sh b/tests/lettuce/run_python-tool.sh
new file mode 100755
index 0000000..e93068e
--- /dev/null
+++ b/tests/lettuce/run_python-tool.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This script runs the specified python program, referring to the in-tree
+# BIND 10 Python libraries (in case the program needs them)
+# usage example: run_python-tool.sh tools/xfr-client.py -p 5300 example.org
+
+. ./setup_intree_bind10.sh
+$PYTHON_EXEC $*
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
old mode 100644
new mode 100755
index b8e85d4..ffe0434
--- a/tests/lettuce/setup_intree_bind10.sh.in
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/loadzone:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
diff --git a/tests/lettuce/tools/xfr-client.py b/tests/lettuce/tools/xfr-client.py
new file mode 100755
index 0000000..662e1ed
--- /dev/null
+++ b/tests/lettuce/tools/xfr-client.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# A simple XFR client program with some customized behavior.
+# This is intended to provide some less common or even invalid client behavior
+# for some specific tests on outbound zone transfer.
+# It currently only supports AXFR, but can be extended to support IXFR
+# as we see the need for it.
+#
+# For command line usage, run this program with -h option.
+
+from isc.dns import *
+import sys
+import socket
+import struct
+import time
+from optparse import OptionParser
+
+parser = OptionParser(usage='usage: %prog [options] zone_name')
+parser.add_option('-d', '--delay', dest='delay', action='store', default=None,
+ help='delay (sec) before receiving responses, ' +
+ 'emulating slow clients')
+parser.add_option('-s', '--server', dest='server_addr', action='store',
+ default='::1',
+ help="master server's address [default: %default]")
+parser.add_option('-p', '--port', dest='server_port', action='store',
+ default=53,
+ help="master server's TCP port [default: %default]")
+(options, args) = parser.parse_args()
+
+if len(args) == 0:
+ parser.error('missing argument')
+
+# Parse arguments and options, and creates client socket.
+zone_name = Name(args[0])
+gai = socket.getaddrinfo(options.server_addr, int(options.server_port), 0,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP,
+ socket.AI_NUMERICHOST|socket.AI_NUMERICSERV)
+server_family, server_socktype, server_proto, server_sockaddr = \
+ (gai[0][0], gai[0][1], gai[0][2], gai[0][4])
+s = socket.socket(server_family, server_socktype, server_proto)
+s.connect(server_sockaddr)
+s.settimeout(60) # safety net in case of hangup situation
+
+# Build XFR query.
+axfr_qry = Message(Message.RENDER)
+axfr_qry.set_rcode(Rcode.NOERROR)
+axfr_qry.set_opcode(Opcode.QUERY)
+axfr_qry.add_question(Question(zone_name, RRClass.IN, RRType.AXFR))
+
+renderer = MessageRenderer()
+axfr_qry.to_wire(renderer)
+qry_data = renderer.get_data()
+
+# Send the query
+hlen_data = struct.pack('H', socket.htons(len(qry_data)))
+s.send(hlen_data)
+s.send(qry_data)
+
+# If specified wait for the given period
+if options.delay is not None:
+ time.sleep(int(options.delay))
+
+def get_request_response(s, size):
+ """A helper function to receive data of specified length from a socket."""
+ recv_size = 0
+ data = b''
+ while recv_size < size:
+ need_recv_size = size - recv_size
+ tmp_data = s.recv(need_recv_size)
+ if len(tmp_data) == 0:
+ return None
+ recv_size += len(tmp_data)
+ data += tmp_data
+ return data
+
+# Receive responses until the connection is terminated, and dump the
+# number of received answer RRs to stdout.
+num_rrs = 0
+while True:
+ hlen_data = get_request_response(s, 2)
+ if hlen_data is None:
+ break
+ resp_data = get_request_response(
+ s, socket.ntohs(struct.unpack('H', hlen_data)[0]))
+ msg = Message(Message.PARSE)
+ msg.from_wire(resp_data, Message.PRESERVE_ORDER)
+ num_rrs += msg.get_rr_count(Message.SECTION_ANSWER)
+print(str(num_rrs))
More information about the bind10-changes
mailing list