BIND 10 trac2435, updated. 61a7a86532aac5a0b84cadee0330f0eb20762ba6 Merge branch 'master' into trac2435
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Jan 18 13:49:01 UTC 2013
The branch, trac2435 has been updated
via 61a7a86532aac5a0b84cadee0330f0eb20762ba6 (commit)
via f9169a71cb277c3733a8ff7d0fea87762196762d (commit)
via 7d4b55818c8510a180889b157bfb360ef601af1b (commit)
via 71e25eb81e58a695cf3bad465c4254b13a50696e (commit)
via 65eb1db45415266f4c3c435b799f066eaf96998b (commit)
via 13674dda1cf3f4f16964c7d5fc72559e3d90b8c2 (commit)
via 5a326976867ae6a6f4c98d056b40d36ff1dbac22 (commit)
via 1cee583601a4e084bf54cf90e0d52b79f0783700 (commit)
via 715a6f61177570c411f2e63f82ae1b2269a654cf (commit)
via 3b38bc023ff63f7567ebce3d9a1cea9fc8654d1d (commit)
via 27369e24c8d4acca1ab5905245b218e31747aee8 (commit)
via 804f48610620ea057b91857ed6d3c993f796c444 (commit)
via 7800a308cb8db2193f8406e1ef4db1f7f6dd0332 (commit)
via cc7b41e7ce61e745602748346c4b00b8b864bdef (commit)
via ac0eaa438f7b76f1f58d54070313e9ffc5417c26 (commit)
via 9a0d024c97ba896c577d59db04a0337d3c70221a (commit)
via ccf170eee20359d3661fc70a7ce02b965cc90a69 (commit)
via 76c52a9395ff0b7bcd9b911308d43da8171a7cd5 (commit)
via 7d705b35fe149cc0c6b93211f2733537b05bd905 (commit)
via 016a19585f32b9bdf57fc5aecdc3b073f3d1a482 (commit)
via 7cbed6378d88f921cf33b12d18d78e56400b4702 (commit)
via a3e0f04e9f735501a2ef08b7d3c0de3f22849fcc (commit)
via 58df436a73f5b3cbe6177aea37383c3ec00ca76a (commit)
via ca0fc023a12228a9ebc20e90921f8ab23ec3274b (commit)
via 30e71e8bfdb6049265dca14a8ce62b989fe67118 (commit)
via a032d84ef41ca563063fa15ad34250f87c05eb65 (commit)
via c3edda086f948bc9c6bd46c8b9198a2e5261eac2 (commit)
via 761ece75fb3184fc28ddedcb742cf0c94fb5ef5e (commit)
via bd51648e6e9c8eac8943db455e48b26c8b196b14 (commit)
via e906f116b8125b58fefb62a14281bbf36b2d7941 (commit)
via 8e5e30b800b786cbcf8ff12db820bb1a7c75d69f (commit)
via 9f079c662135d45fac7be5b0b9282e7542c55e81 (commit)
via 2f5ba8955ec66c1c7af60b85272f03383b256fc0 (commit)
via 61e7fa26cfe5ec6cd4a381bdc9e5bc25437f2620 (commit)
via b6d00d5fb3657b555c9f25d4c23721d277a7d7a4 (commit)
via 68709a07c59441cd101e750d72049a6218f0262b (commit)
via 019ca218027a218921519f205139b96025df2bb5 (commit)
via f09298e6e1512b32462f41a4ced3dcc1950e18aa (commit)
via 54cbb54f358dd83e6c87f23ed17c5df754a28dc7 (commit)
via 4baf3a4dcacaa5d2fe57dba1a8cb1eee68cd0f21 (commit)
via 6e2a0ba86f0607f3e9822a178a223e8a922de47a (commit)
via ec9d85afe154206e95b683eaa8733e0f453f4090 (commit)
via 0b3abc02d6b943026d5e60f6376d5c20d913bcb7 (commit)
via c9d46273b40b9be9902deaa081be0f1216d287c6 (commit)
via 6278daf2fc808fd4a311a97529815db1ef216e79 (commit)
via d86e308e8d1cf3c569f9ad1edf17bb4289da954c (commit)
via 60606cabb1c9584700b1f642bf2af21a35c64573 (commit)
via beef799a018be9b0be103973c9fa477a7d0ca6fb (commit)
via b82c412af1ef36000368f817f25aef23cc36728a (commit)
via 360a5bb6cb72862edbbe9a4cb0074360f1385e21 (commit)
via 6478f4a1f3d3fb66a7fc25887135bdf3403538bd (commit)
via 924221ee423612e1389ff98a88ddc127e283aa02 (commit)
via 7a8402a585b391394949582c14ffb9182c757979 (commit)
via 82a1d5c34e951687d67a6313fb42d9e75768adbc (commit)
via ead15091870f4dbd5a87640be3493e36d9e9da73 (commit)
via 1c54eac8eb70d3fc7c16b83a1269315e710021c1 (commit)
via bed3c88c25ea8f7e951317775e99ebce3340ca22 (commit)
via 2d95931bbb2efb3ae30b6fe7219958e40b51c4f8 (commit)
via adf283b9aa964639693d23a4e809fe67657583fb (commit)
via 84bc0b71b8c31013d702c206a3fb1b4d1b93e514 (commit)
via e567eee7d939fa75ac7ccee625178e179e47ad98 (commit)
via 3072c500b4c563cb22b7cdae805cdb6c1e99a408 (commit)
via 76ca31298b2e7f5d85904045aaa6560bcf7ff8d6 (commit)
via f603440ab46a86436b2208697f36bd9726fc0e03 (commit)
via 11b14c65cb4c01d5d5278f14ac5b292842f91e13 (commit)
via e81f84bdabd3a5b7370bd2f4c41d9c7de333fafd (commit)
via 989e0535b0a1b2746aab98aa00aef995eeb4696b (commit)
via b32cd0ea8778cac7a9f9e991d3c978dc7faf93cd (commit)
via a095d57b989aca503c22bfe2121df389abdbd392 (commit)
via a8e4e8678710187bbd044689a0ed603f5182fda8 (commit)
via 5477d36770d789ce36b2a74acedf64cf63e79460 (commit)
via 905a40f2a1c627edd9bbaf26166311ba7e623d0e (commit)
via c924b23383294fe5b7a21aed00e62fbfd8e88d1c (commit)
via f000b6b54d4abfad7abe39ed0b817a29a1f073f0 (commit)
via 6b78fee487016e4920ab469448c559752523d474 (commit)
via b8b0b9c2b4c0ef559c027033fdcb95e8ff61d9fb (commit)
via e4f44ed5418cca60318c89472defcf24eea93826 (commit)
via db45e761f5f8c140d80d242033642e2d929a2a2d (commit)
via 36d56446c3758b1d8e6c005fabfe8432b9751131 (commit)
via 2bbe63dbbdda8e75fa60df6ea19f4b0354edebeb (commit)
via c4904fef78c9bae9fdff9aa9852d7b8dca3a9ba8 (commit)
via 0c40d70c28cda1bc61871cd0e26b74656f45114d (commit)
via 37a27e19be874725ea3d560065e5591a845daa89 (commit)
via 042731613c48d83d96bd3a4911b397977ae783dc (commit)
via 6926ec8a661213e78e73922d093c9883a5d16eb1 (commit)
via 331f01d140ccc6d2f9a37fb483581bfdac1f5cfa (commit)
via 79007cc52b34b9cca6259c86a479ddc5100f15f5 (commit)
via 26a6ff11a1634de2624080eeb424df4d782496f4 (commit)
via 6010b776a5a76c7c9df5025e86c7902992750615 (commit)
via e39b61088924933e1335760d21cfa10e0cfd5425 (commit)
via d365482e0e132c96812b7b99c465582a027035f2 (commit)
via 533c4afdae8ff16eb7d7b1f5529dd572e1098cf3 (commit)
via 21794c5df650ecf28f55ed95cb12d278945ab3a5 (commit)
via bd9b58b2875c100b68e3f246a09975bde0a13e16 (commit)
via 4e973e228e15b24d70b36c18f79180833aff1839 (commit)
via b3a9b362643eddb59ef78ff392cf091d0c199163 (commit)
via f8ad67f7166f71b5f78721cc2d7df85c2a859bbc (commit)
via ccd941541d51c3a3eb3df162e094637ad8317f63 (commit)
via 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8 (commit)
via d5310a9cec9a3ece6b0cb323ea2a933caca127b9 (commit)
via 564f603d2056a823b3436acf0183a9abeec35ecb (commit)
via 856d60a96bbed5735541659d7c12c51fc055a6cb (commit)
via 792c129a0785c73dd28fd96a8f1439fe6534a3f1 (commit)
via b85908b8e8321ae1b1ab139c7182a5026bc7f73d (commit)
via 3e191cf2889f9ef77561ebac18e1cc019d1bffe4 (commit)
via b789222291eeb63126dc1c249ed89d4f4517db63 (commit)
via 6e4caf9dd4f4239776d1e05efbfe13987e5bf9a5 (commit)
via 2548205bf705e3f2d9aeb75e35eb112206476d03 (commit)
via 8399e2b6d96d18197dc27b9f4bee1f5051f6c7af (commit)
via 3c162ca506c8fc08ab93374521807b2132e81c24 (commit)
via 5eb2ff86d745543185551758bf31132596ad20d9 (commit)
via 0de32e7fe21e18069d26e3d282ac8078c852f004 (commit)
via fb10ec4e31a94ed4c98fa83c5ef3064f8e53a39e (commit)
via 83e71312ca3631e5a8f2364bb8434d69ae000ee1 (commit)
via b55b8b6686cc80eed41793c53d1779f4de3e9e3c (commit)
via 7234eee9cf529a98841ca502dde655bd15f4299f (commit)
via be570bcaf88318286027da7da40a600cbe4223e9 (commit)
via e5a354694f4d0192132d00e25223d16eb24f25d7 (commit)
via a92495cd29fbe405bef744b98d01ff200caa05c6 (commit)
via f516fc484544b7e08475947d6945bc87636d4115 (commit)
via d509593229c0424fc430766f56d5e5947cb3dda8 (commit)
via f1e9ef1f4906cfc43cf76308675d24a851739105 (commit)
via 96e05ecaa1e2c19c28ff5d1f50efdcff7e3e7d5c (commit)
via c6f04a39f70ad71571779b734e7a3593e6c2f69f (commit)
via 96f973347163030737690174068f51c193a2f4e6 (commit)
via c85c06e93473453547d4a378ebc3c12ef74ae68c (commit)
via 202d9dd4ef7d116f844029a34c36fbdb48921ba0 (commit)
via 18831888667fe9363c22d3d19db9b0f98e56ef70 (commit)
via b0a3df2d384689dad61a47084b5dfa5a00918885 (commit)
via de2886172153001a862453ee4ed73bcd07db2ae0 (commit)
via 58c136da23483524f053d3caa20210941bfa13db (commit)
via cb50f4f6d306ebd40d7ff03b1494dc52b96b191d (commit)
via 7cee8494a729694402e9ce19d257d643888b7a4c (commit)
via 15a69fd77a14825ca0deb1cba52690bf0ea33195 (commit)
via 50e76dc960ad76ec97004832d718cb337390fce8 (commit)
via 3e9a69424117d8f9841d76cf71dab353b6aa5435 (commit)
via 890e0b0b7df76e61c1638744647a793143537722 (commit)
via a6ce7e4332223277ca50cd68a65e7e404621717c (commit)
via d83fc2a12f36818bdd60c87e61d01be7a5dfa4e5 (commit)
via 46efce68984c6ab8c5d02618e2b9ca21bf46de06 (commit)
via 7bea86f90950a22f72d0c6271d7086ab59abdfa0 (commit)
via 1f011110511b06ca175f19fecf0ce522140c6f95 (commit)
via 9e85dc2c98c9fde0f1d73c1d6c0d0d6188b41439 (commit)
via 36622df85e6b1b7ce4fc621fbfaa21d2b35003d8 (commit)
via bf592d0607a639ff454dcdc820a76ae0eaacbbf4 (commit)
via 2ed60b95b8cbdff758e08062fc82c8d8f7b367ac (commit)
via 059fcd024b1611a7c5ca52a6f7055f7c9d14f106 (commit)
via d078381e41cf375a3504655ec921999828c284e2 (commit)
via c6e109f7d68347703baa542d0a913e97394b8057 (commit)
via 9fbb0ae95365974d80a87704f64d6e4907dcb52e (commit)
via 4cb0974a3a642d06513ecc43743109bd92e45a08 (commit)
via 573d2b1fbc3145d18ec71621f56e5c74a328ed3c (commit)
via d831b49beab09fe65b95d39858486824e9b9a4da (commit)
via 2d107bd1f98f6a3aed2120213d713111246e6535 (commit)
via 57e0fe723d9769cb893ae168f7d9374280670c4d (commit)
via 28f38670d37fe76c2481df6b8f82284ce4949de2 (commit)
via 9a2fd23cdd58afe5dbc932dead8f10b0ac22605c (commit)
via b8e5414ee0445b3b0141203470ac07f42d7bbc32 (commit)
via aa32f38e127d1b3b3d4e1349fbfdcac155dc5c5e (commit)
via 40502d5e2c6cbe71143e842ac7908e8a55b62324 (commit)
via c6981a0eed4cb0c68f747fa7f758e53dbfdaf5f7 (commit)
via 385f156f420aa17a1d6f07bcb17a87075f9d4492 (commit)
via 0fe0d44af6e81f2e5230515237fcc84b42f37d85 (commit)
via 60b979b7088f9e282b173c8d71792e2c4b20b57c (commit)
via 0974318566abe08d0702ddd185156842c6642424 (commit)
via 31147f0f82fd1e1b8ead164153599269822517ea (commit)
via 8679b4053024172db8e3d55b7ed50a855efc77c0 (commit)
via 5530bff261fd921726fce87c2d296da5271c639f (commit)
via c1fd5c76dc679f0156748f66f58f3d7220703332 (commit)
via 234e86994e8af3f68d830ec22e9857aa34c78983 (commit)
via c466b2fb0a4888db16ff0888dc0f8c9875d6f43c (commit)
via e2f99db35b161d951e60aee4728118cf41c3b3d4 (commit)
via f47a693087d2583eee59f97e9b43c40535443688 (commit)
via 9faa2925c3934e3526b5b24694ec0986a6303d42 (commit)
via 469a7104b7231ef26e9f08e85e9f4acf035398c4 (commit)
via 4d7307519b21a0364350cbac4560f6693430cd6b (commit)
via 86f542bb1fecca6e8d4b93a3462649b9dc17805f (commit)
via a12ebb8fdcf246c1a096d62c624185c96f88b186 (commit)
via 0fd48fc7f5406e8cbd8b459a7fc5231053519d3b (commit)
via df8823fdb350670a1e5333b335b59a56e8b09b4c (commit)
via 3e100854e0a85d215d614729f6d429340be1ead2 (commit)
via a146bf1575760b55554d3d77520ea0900b9a4d51 (commit)
via 1deefe2736bcc2b61e07f20ea732d178d84ab772 (commit)
via 6c7d6aae42af702f8b846b3984c09854a5a58539 (commit)
via 09e8ac90d96a468beb509c4da6e9dd74975aa1ad (commit)
via 20419496af5c9374c7858251e1b857413d1f7d17 (commit)
via 3965fb5302e46eb4e7763a328add0a83687a8309 (commit)
via 810c95b11f458b18c196a39cba8f3d47ac1d6cb2 (commit)
via 85da7d68f1eff606608bd46df43a8f57436a0131 (commit)
via d4eda56344420aafa49d1c0d91d1a6a95c7a4707 (commit)
via bd8ac0ce36dee77dc4865239576745db7588db60 (commit)
via da5c7b1faa2363bbca6db163364e6e0073bcc3ab (commit)
via 823f41b44713d161cbba7456914387e323595220 (commit)
via 0e90f478241569b4c63e86eea31553973bdab394 (commit)
via 0232cbe00f15efbe750b21c80973feb220bdc6c2 (commit)
via 67eb8ca010320d7c942ba154d70456d8e9981fa7 (commit)
via 0b722ec0d7813e933db3919df492dccf11a3b4eb (commit)
via b0732ce0447db537b3d643735f16aad09ddd4c7c (commit)
via 1c2b85d7d1c1448d78b8f2a9927b7952ef42e39d (commit)
via 6d3fb27de20503a07333bbc9e053451a80442a41 (commit)
via bef01123fb5dfaee927bc58da465109240811a18 (commit)
via 62794b031c00451e7bec0a0b94b4a2dadb42ae0e (commit)
via 82041829bd8cc7400d0fee9d08e4cf1091c25a70 (commit)
via baa1ddd9057d0f10f85faa67f92e94268189dcd1 (commit)
via 5379cabb61c3daff05c541ab6c9675a8a3ebfed7 (commit)
via 30cbe355d7fcccf3e06472e5fd04c9de870adbe6 (commit)
via 2629140f12accdfa6b4de4f59e5a76b0cc10ecb6 (commit)
via ea8082a263cb6d2b69764da2322b2c483913e5da (commit)
via 6c20066d5613dfda246d598f47b9e779eef9c70b (commit)
via 2aa422106dd081138c633ad2f9a3ede0ebdbd94a (commit)
via 14220fefc56f7970d6b8bfd316c7ee234dd416d4 (commit)
via 34596a05508cee3e48565a4082e9f6e9b815ad41 (commit)
via 6b045bcd1f9613e3835551cdebd2616ea8319a36 (commit)
via 32e13fbffae1a721a44f8b764ecb5ee066e64bf5 (commit)
via 2d46b704c62b47c2313d80b5afafbb180c1b7853 (commit)
via ae8851eea4d83050ad2a8e17fc6b8f0b11364fdd (commit)
via 2078a3a34f198220806f9eabcf6fa7f6f0c9288c (commit)
via 5ee9971dc60341992cf7da807dbec32811dd14d7 (commit)
via 025db8d7a207b39174362981868742f1b80bdf69 (commit)
via 00845ee8d7366b80e23e349b7cf8cd9d52551785 (commit)
via 5d2a22ac8f92c53a89a5f4507ac3946529b9d49e (commit)
via 4323bf82f07269a4f99a951b659749a5a1234ef2 (commit)
via fb95fbd37114f9c8fed764979cc5dc9124e3439c (commit)
via 96e4248d61ccbcd23c7563f0a36d5443b607ea02 (commit)
via ae5693b89371208d5d5d8e884b52362f861c633f (commit)
via 778e6009722520325aaa1efc98240fffc8f6b6d1 (commit)
via 59b176e95540aacd2b0f167cba350d7fa18771db (commit)
via b52e736b9e5079aedf5252f04d019a80292bfc41 (commit)
via 9b0a73f8a2523480f538747d579c940534ac5ac5 (commit)
via 36fea252b03a38a0a86b830bc6a3373a88966413 (commit)
via d4bd5936f52d20ba21865858861dc358f531da04 (commit)
via 19f2ceb60860badfc183f9d4e7d54ac9105ee296 (commit)
via ce6444b69feb909e5c8212e7335eeca14a2b11ea (commit)
via 33ba1827b513aa735a483f6927667a9e872a2207 (commit)
via 23035066d0c07c7b83120d3cba9efc1febe18ff5 (commit)
via 92e4f34d7c74ff7b34599e1d3e8d0622ebc633d6 (commit)
via c2e9d347dcd2fdb0f9092dc4b04579d1c412de46 (commit)
via 90f997d7b29824aa848beb73745e5009c415956d (commit)
via 080d47deb3088adddd96434acfd1c82bd14779c9 (commit)
via 9f5102a30e4cb8da4697b3561206957b9a2aae05 (commit)
via 0758ead7f4044e0867d4f953427b6c10f7afab45 (commit)
via 9ab72138d43cb21a620c021a24e6a248b3dd77ab (commit)
via ffe973e3df51ab36e17087eda23b1042500d39b5 (commit)
via 857d7fbf031d29b5b66ebe38fec725dde35008fd (commit)
via 514b87c307fc834f39f0be155fc9bac9a6508d70 (commit)
via 6d28690c67369927bb0806618f23533a1457eb6f (commit)
via 029be672c7bf1b936859e811a17d45d04215ecd4 (commit)
via 1a0577f9f348a74eacc959e091756f0e7a71accb (commit)
via 5ec876c98d10dfdb0675ffcde709b5ce9871465b (commit)
via 09dc37b8319af571d2e7afb0bf5b6da82cd174f6 (commit)
via e17ddc8b0f6726a185e982c22dbf787351c22cdd (commit)
via 8960a571bc2db7129d4ce8861f83808fb71dc81c (commit)
via 3ff7531f6be864ca6965a60ef1e2744e945a3569 (commit)
via 786673c6998d886be73950a99a0292344b750cea (commit)
via fe40dade09da3ca910f034e650db2a3d5a93a49b (commit)
via fb8433b523f854b3978ef1c868d573e172a5a76c (commit)
via 5d2f35c760be8fe9aa54f566461ca8a9c5d0db78 (commit)
via 5d8d1e296a4eccc34ab0c19acfa9a4f39a63745c (commit)
via 08dec0f8e0129ed43373dca3a335d752a3fb5e2c (commit)
via 6ed60d209f8c6d7d7d4978b19a2feb6170e2c67a (commit)
via 448686bfad7694acde8b08355dd5921efd4bb2ca (commit)
via 5deb6bbc2df15dc20f36727f0abeaa4d4ad0c17e (commit)
via e44d06354aa0785cf5bd247358e59f7dfe69f5ad (commit)
via 17cd3618c21b12d7867d9e5e4dba81c1cee46cb6 (commit)
via 376b3b0760c5198dbf5ccf740c666ed9c9acaf45 (commit)
via 4beebdd9209a2a75b33068f4d1811b2664b957db (commit)
via db55f102b30e76b72b134cbd77bd183cd01f95c0 (commit)
via cb9f017de088330bd6e4107bad642b5e4ea66ce9 (commit)
via 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb (commit)
via daf78976a3df91027c2a82cd0a794255b60507b0 (commit)
via 291591772d83ff6b5ec9412b03624d2c9f13dc82 (commit)
via 28a43fa59ffd4f772104a76e3636b8c54b329aa3 (commit)
via 9065335de2f05d3bc1d874b933ba80b51feb68f6 (commit)
via caf95c197aa4abb916d8e27d3a5004ebed2e6108 (commit)
via a32568b7b3006315e79ee411b50db8cc4dc435a7 (commit)
via 3c702b8966081eb2d73914754abd247350957bdd (commit)
via 8b1a0145b79147510ccde00b28e5124b8951e4d1 (commit)
via 19bd1a5a0b222ac2473793d87539f76a34cb1bec (commit)
via 27e7210b344cea5c7098bf233481d5f2bf7cac97 (commit)
via 230078e5f45f29fec72e35a77e2fd897fb6d3fb7 (commit)
via 77b42eabd7630b5ba7f5f9fa383de73e0e7fe49d (commit)
via 31a4bc5aacc85f79416c6c4da2e33e642077e9f9 (commit)
via 89f331dff577268f90d885dc871cd2a152940ef8 (commit)
via c6c569cae91e19aa6267081ef9926f52b87d903d (commit)
via c8dcb23d366d559cb685b64514f49d7222df3335 (commit)
via 3ac15510bb80c1f7fe76779dfa9ff254eb5cc796 (commit)
via e1c2233fab06d393eed6e16fc09f9bf672963112 (commit)
via 5f8c941e31630163287b5197b6185dae9b03de13 (commit)
via 17553447e571eee4690ad90caf8365977d57c4f0 (commit)
via 5054d0ed85f4645a8019e77c860e7415042e6d05 (commit)
via fb95fe7735c5995f727ea42dfafcad820a3764da (commit)
via 61c303c8f15dce6a985e5d0164a50844b6c64558 (commit)
via 6eb4014782afae7660dbdc291c315a211e5ceed5 (commit)
via bdb56ee0da86148611e50095c9d70dfe4e4f4b7f (commit)
via 17641de21f0430981b554af5595711a3bcf2d699 (commit)
via 2039944b9b5a639d61466c197660a81f047013b3 (commit)
via 2fa6a93d7f49f8073bc919620afc2cba4c7c0ce3 (commit)
via 357c297f73a57539c9b90f7f80c4bebbdf2f8db8 (commit)
via d8a4ee26573da0832ad2efc424e6c9db9760fb85 (commit)
via c661badf19c5e9593d8c4ba719b86cd96b85f787 (commit)
via fed1aab5a0f813c41637807f8c0c5f8830d71942 (commit)
via bd8497df6615f58b2a6de701cf2a8f02c4d57d9b (commit)
via a5aab271f44cce29af4344ae8c24da02167deb05 (commit)
via 57b40ee87d9689e6479d7fe4a2cf9903db3a3644 (commit)
via 2156cc8503238162e22f418632837003593c8e3c (commit)
via 61386c685763040bfcb779f0224bf6867a4dceb9 (commit)
via 44bd04d531d1881d7c9a8fea9bce4b6c10aea16d (commit)
via ef19026395320447f81738be3fd840e278ff8da2 (commit)
via 7299bfac202b013e968ff19c1c3109d78745ed22 (commit)
via bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2 (commit)
via f64ff7004ddec6c81538b2fc5d6827de6b79cfb8 (commit)
via 8d844f040f2f764f7be009c17d25b06e159b1924 (commit)
via 4a4642ad16734522436855cfd8e4d09c5ce32d06 (commit)
via a878a8107d48c5b1c476930cd4eb769f609fd609 (commit)
via 8474c4bf697ccf594ca3d08dd7b1f327a44e5e82 (commit)
via 71c5b61bcf71fb8ef3382569ea0eb059b0cca430 (commit)
via 667d94d93788e24158ebbec640f006f49b7d438c (commit)
via 15f662d68bbce471df63609cf8deccb280670048 (commit)
via 96c1a3cc7becc69e4bb24cd3e40aaa605e3d90e3 (commit)
via 774a4996dafc7a5809ab0ae08851d412e996e970 (commit)
via c864447b8ec187264fac032be1561c77bb3103b6 (commit)
via 95483cb529b42683002528186eb29d6854e7ce0e (commit)
via 8d8605b657d5e554f0c496143fe1a3b3cb8d3ea8 (commit)
via fdce9eb73133bb52609ed6ad51fdb77b28447eb3 (commit)
via b0bc2d360163be4126f6a88b73a3c17a18714736 (commit)
via 772ce4c2888583e2fe1e9959c705608f6d1b7e98 (commit)
via 00ae760dd4d30c5944dfb70a892c15004a1f07ee (commit)
via 94cb9c2b880da087af403da4b5b765ccd77ad594 (commit)
via 7e61e0a0c0d3b87a7e1033a353a273b1502caa8d (commit)
via 9394663ad4f49908d99dc2d2a47ddc0a72871f4a (commit)
via 1b69710afed58b9f7ebff61229f3f29e487f4026 (commit)
via 4d6495aa80132d3a67ad4ef9aa76d75683772a80 (commit)
via 530e62ba1a41f63a8dbcd0838fb5cfa95cb3816a (commit)
via ed7622df90ee865b5be03b0d13ed5977466bebfc (commit)
via 72beaf8964363734d4f5e7b4b11ae7b8b4b5557c (commit)
via 62a23854f619349d319d02c3a385d9bc55442d5e (commit)
via b07567633570ebffebb13e92ae71d429967a7cb8 (commit)
via 46cf8771c26b220b60ca8f30332442b5a7dbcbef (commit)
via 593eacf16fe69fade5eeb71704ac25c6e046421b (commit)
via 393c70b7a308f5d816a87cf21e017351dbf09260 (commit)
via c980c37cce2a445ec40f2c526cd89310cf7c4941 (commit)
via 44f6dc45fffe9a44d8700bbb601dab169568f7ac (commit)
via 4114ac95b51b5551e38bc277860abf14775cc817 (commit)
via 16bdc3898aa70d2145872a5277f60711c32f2bd9 (commit)
via b40cd6851ace87bc97a095cf76b2a0ddee3666ed (commit)
via 16cc994d2f3e15180fc022976062d616b0c05f38 (commit)
via 89f2d7507297c5e3c1daf815f45e54499d029e27 (commit)
via 6f27e6fc9819b5807703ff141e31a0d7a6800a26 (commit)
via 870ee769dedab453f6fb65082884cd2b45d1ca14 (commit)
via 2ad9f33c18a74df23e3ca10fd5246289093e2768 (commit)
via 079e629bdec110c1b07376645361fbcb2e467043 (commit)
via 165e2775424f0377b5eb8f574d83c0d0dcd67a4b (commit)
via 34cb2bf92262addf0fb401365c23773dee3f3e89 (commit)
via c55cdfd6318072c9ae7005298fca07615065c97a (commit)
via be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
via 95cc8210a2f620d6a2a886cf7577ed2d257d2bad (commit)
via 6ce0831748423ab48611785f29ee999beeaf8322 (commit)
via 32b6b02bc06bbf0303639964f3544e9cba4b138d (commit)
via e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
via 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
via 318530edbf905ff288f527ef73bb88da4c57b548 (commit)
from 09c47d4fef3388cd1aa2aa7db402da67c4968d3f (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 -----------------------------------------------------------------
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 3 +-
ChangeLog | 148 ++-
README | 24 +-
configure.ac | 70 +-
doc/guide/bind10-guide.xml | 95 +-
examples/README | 2 +-
m4macros/ax_boost_for_bind10.m4 | 112 ++
src/bin/auth/auth_messages.mes | 8 +-
src/bin/auth/b10-auth.xml | 48 +-
src/bin/auth/tests/.gitignore | 2 +
src/bin/auth/tests/Makefile.am | 38 +-
src/bin/auth/tests/gen-query-testdata.py | 98 ++
src/bin/auth/tests/query_inmemory_unittest.cc | 123 --
src/bin/auth/tests/query_unittest.cc | 1108 ++++++++++-------
src/bin/auth/tests/testdata/.gitignore | 10 +
src/bin/auth/tests/testdata/Makefile.am | 7 +-
src/bin/auth/tests/testdata/example-base-inc.zone | 236 ++++
src/bin/auth/tests/testdata/example-base.zone.in | 7 +
.../testdata/example-common-inc-template.zone | 5 +
src/bin/auth/tests/testdata/example-nsec3-inc.zone | 16 +
src/bin/auth/tests/testdata/example-nsec3.zone.in | 8 +
src/bin/auth/tests/testdata/example.zone | 121 --
src/bin/auth/tests/testdata/example.zone.in | 6 +
src/bin/bind10/bind10.xml | 2 +-
src/bin/bind10/bind10_messages.mes | 11 +-
src/bin/bind10/bind10_src.py.in | 79 +-
src/bin/bind10/tests/bind10_test.py.in | 97 +-
src/bin/bindctl/bindcmd.py | 12 +-
src/bin/cfgmgr/b10-cfgmgr.xml | 2 +-
src/bin/cmdctl/b10-certgen.xml | 2 +-
src/bin/cmdctl/b10-cmdctl.xml | 12 +-
src/bin/dbutil/dbutil_messages.mes | 6 +-
src/bin/dhcp4/Makefile.am | 2 +-
src/bin/dhcp4/config_parser.cc | 1267 ++++++++++++++++---
src/bin/dhcp4/config_parser.h | 129 +-
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 3 +-
src/bin/dhcp4/dhcp4.spec | 138 ++-
src/bin/dhcp4/dhcp4_messages.mes | 91 +-
src/bin/dhcp4/dhcp4_srv.cc | 285 ++++-
src/bin/dhcp4/dhcp4_srv.h | 67 +-
src/bin/dhcp4/tests/Makefile.am | 6 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 1021 +++++++++++++++-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 941 ++++++++++++++-
src/bin/dhcp4/tests/dhcp4_unittests.cc | 5 +-
src/bin/dhcp6/Makefile.am | 12 +-
src/bin/dhcp6/config_parser.cc | 1271 ++++++++++++++------
src/bin/dhcp6/config_parser.h | 136 +--
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 3 +-
src/bin/dhcp6/dhcp6.dox | 32 +-
src/bin/dhcp6/dhcp6.spec | 70 ++
src/bin/dhcp6/dhcp6_messages.mes | 75 +-
src/bin/dhcp6/dhcp6_srv.cc | 197 ++-
src/bin/dhcp6/dhcp6_srv.h | 59 +-
src/bin/dhcp6/tests/Makefile.am | 9 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 656 ++++++++--
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 409 +++++--
src/bin/loadzone/.gitignore | 1 +
src/bin/loadzone/b10-loadzone.xml | 2 +-
src/bin/loadzone/loadzone.py.in | 29 +-
src/bin/loadzone/run_loadzone.sh.in | 10 +-
src/bin/loadzone/tests/correct/include.db | 4 +-
src/bin/loadzone/tests/correct/known.test.out | 4 +-
src/bin/loadzone/tests/correct/mix1.db | 4 +-
src/bin/loadzone/tests/correct/mix2.db | 4 +-
src/bin/loadzone/tests/correct/ttl1.db | 4 +-
src/bin/loadzone/tests/correct/ttl2.db | 4 +-
src/bin/loadzone/tests/correct/ttlext.db | 4 +-
src/bin/msgq/Makefile.am | 13 +-
src/bin/msgq/msgq.py.in | 55 +-
src/bin/msgq/msgq.xml | 2 +-
src/bin/msgq/msgq_messages.mes | 88 ++
src/bin/msgq/run_msgq.sh.in | 18 +-
src/bin/msgq/tests/Makefile.am | 2 +
src/bin/msgq/tests/msgq_test.in | 28 -
src/bin/msgq/tests/msgq_test.py | 3 +
src/bin/resolver/b10-resolver.xml | 4 +-
src/bin/stats/b10-stats-httpd.xml | 8 +-
src/bin/stats/b10-stats.xml | 2 +-
src/bin/xfrin/tests/xfrin_test.py | 4 +-
src/bin/xfrin/xfrin_messages.mes | 12 +-
src/bin/xfrout/tests/xfrout_test.py.in | 2 +-
src/bin/xfrout/xfrout_messages.mes | 7 +-
src/lib/cc/data.cc | 4 +-
src/lib/config/config_data.cc | 2 +-
src/lib/config/config_data.h | 12 +-
src/lib/config/tests/config_data_unittests.cc | 11 +-
src/lib/datasrc/client.cc | 14 +-
src/lib/datasrc/client.h | 36 +-
src/lib/datasrc/database.cc | 20 +-
src/lib/datasrc/database.h | 33 +-
src/lib/datasrc/datasrc_messages.mes | 8 +-
src/lib/datasrc/sqlite3_accessor.cc | 20 +-
src/lib/datasrc/sqlite3_accessor.h | 4 +
src/lib/datasrc/tests/database_unittest.cc | 156 ++-
.../tests/memory/rdata_serialization_unittest.cc | 27 +-
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 48 +
.../datasrc/tests/zone_finder_context_unittest.cc | 32 +-
src/lib/dhcp/Makefile.am | 13 +-
src/lib/dhcp/duid.cc | 10 +
src/lib/dhcp/duid.h | 5 +-
src/lib/dhcp/hwaddr.cc | 62 +
src/lib/dhcp/hwaddr.h | 67 ++
src/lib/dhcp/libdhcp++.cc | 28 +
src/lib/dhcp/libdhcp++.h | 15 +
src/lib/dhcp/option_definition.cc | 39 +-
src/lib/dhcp/option_definition.h | 15 +-
src/lib/dhcp/pkt4.cc | 136 ++-
src/lib/dhcp/pkt4.h | 60 +-
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/duid_unittest.cc | 12 +-
src/lib/dhcp/tests/hwaddr_unittest.cc | 126 ++
src/lib/dhcp/tests/iface_mgr_unittest.cc | 9 +-
src/lib/dhcp/tests/libdhcp++_unittest.cc | 60 +
src/lib/dhcp/tests/option_definition_unittest.cc | 50 +-
src/lib/dhcp/tests/pkt4_unittest.cc | 47 +-
src/lib/dhcpsrv/Makefile.am | 27 +-
src/lib/dhcpsrv/addr_utilities.cc | 10 +
src/lib/dhcpsrv/addr_utilities.h | 5 +
src/lib/dhcpsrv/alloc_engine.cc | 334 ++++-
src/lib/dhcpsrv/alloc_engine.h | 121 +-
src/lib/dhcpsrv/cfgmgr.cc | 126 +-
src/lib/dhcpsrv/cfgmgr.h | 98 +-
.../dhcpsrv/dhcp_config_parser.h} | 135 ++-
.../dhcp4_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} | 6 +-
src/lib/dhcpsrv/dhcpsrv_log.h | 66 +
src/lib/dhcpsrv/dhcpsrv_messages.mes | 231 ++++
src/lib/dhcpsrv/lease_mgr.cc | 40 +-
src/lib/dhcpsrv/lease_mgr.h | 199 ++-
src/lib/dhcpsrv/lease_mgr_factory.cc | 53 +-
src/lib/dhcpsrv/lease_mgr_factory.h | 35 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 125 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 24 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 61 +-
src/lib/dhcpsrv/mysql_lease_mgr.h | 5 +-
src/lib/dhcpsrv/option_space.cc | 71 ++
src/lib/dhcpsrv/option_space.h | 189 +++
src/lib/dhcpsrv/option_space_container.h | 102 ++
src/lib/dhcpsrv/pool.h | 7 +
src/lib/dhcpsrv/subnet.cc | 108 +-
src/lib/dhcpsrv/subnet.h | 156 +--
src/lib/dhcpsrv/tests/Makefile.am | 2 +
src/lib/dhcpsrv/tests/addr_utilities_unittest.cc | 47 +
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc | 721 ++++++++++-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 261 +++-
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 135 ++-
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 33 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 81 +-
src/lib/dhcpsrv/tests/option_space_unittest.cc | 150 +++
src/lib/dhcpsrv/tests/subnet_unittest.cc | 170 ++-
src/lib/dhcpsrv/tests/test_utils.cc | 58 +
src/lib/dhcpsrv/tests/test_utils.h | 49 +
src/lib/{asiolink/io_error.h => dhcpsrv/utils.h} | 33 +-
src/lib/dns/Makefile.am | 8 +-
src/lib/dns/character_string.cc | 145 ---
src/lib/dns/character_string.h | 60 -
.../dns/{python/nsec3hash_python.h => dns_fwd.h} | 59 +-
src/lib/dns/gen-rdatacode.py.in | 9 +-
src/lib/dns/master_lexer.cc | 35 +
src/lib/dns/master_lexer.h | 65 +
src/lib/dns/master_lexer_inputsource.cc | 75 +-
src/lib/dns/master_lexer_inputsource.h | 36 +-
src/lib/dns/master_loader.cc | 28 +-
src/lib/dns/master_loader_callbacks.h | 6 +-
src/lib/dns/python/Makefile.am | 5 +
src/lib/dns/python/pydnspp.cc | 21 +-
src/lib/dns/python/rrset_collection_python.cc | 426 +++++++
.../python/rrset_collection_python.h} | 39 +-
src/lib/dns/python/rrset_collection_python_inc.cc | 148 +++
src/lib/dns/python/tests/Makefile.am | 2 +
.../python/tests/rrset_collection_python_test.py | 140 +++
.../dns/python/tests/zone_checker_python_test.py | 179 +++
src/lib/dns/python/zone_checker_python.cc | 229 ++++
.../python/zone_checker_python.h} | 18 +-
src/lib/dns/python/zone_checker_python_inc.cc | 79 ++
src/lib/dns/rdata.h | 2 +-
src/lib/dns/rdata/generic/detail/char_string.cc | 83 +-
src/lib/dns/rdata/generic/detail/char_string.h | 52 +-
src/lib/dns/rdata/generic/detail/lexer_util.h | 70 ++
src/lib/dns/rdata/generic/detail/txt_like.h | 10 +-
src/lib/dns/rdata/generic/hinfo_13.cc | 150 ++-
src/lib/dns/rdata/generic/hinfo_13.h | 38 +-
src/lib/dns/rdata/generic/naptr_35.cc | 273 +++--
src/lib/dns/rdata/generic/naptr_35.h | 35 +-
src/lib/dns/rdata/generic/soa_6.cc | 117 +-
src/lib/dns/rrclass-placeholder.h | 55 +-
src/lib/dns/rrclass.cc | 17 +-
src/lib/dns/rrcollator.cc | 2 +-
src/lib/dns/rrparamregistry-placeholder.cc | 35 +-
src/lib/dns/rrparamregistry.h | 45 +-
src/lib/dns/rrset_collection_base.h | 2 +
src/lib/dns/rrttl.h | 2 +-
src/lib/dns/rrtype.cc | 7 +-
src/lib/dns/tests/Makefile.am | 2 +-
src/lib/dns/tests/character_string_unittest.cc | 97 --
.../dns/tests/master_lexer_inputsource_unittest.cc | 51 +-
src/lib/dns/tests/master_lexer_unittest.cc | 49 +-
src/lib/dns/tests/master_loader_unittest.cc | 3 +-
src/lib/dns/tests/rdata_char_string_unittest.cc | 137 ++-
src/lib/dns/tests/rdata_hinfo_unittest.cc | 25 +-
src/lib/dns/tests/rdata_naptr_unittest.cc | 76 +-
src/lib/dns/tests/rdata_soa_unittest.cc | 160 ++-
src/lib/dns/tests/rdata_txt_like_unittest.cc | 20 +-
src/lib/dns/tests/rdata_unittest.cc | 3 +-
src/lib/dns/tests/rdata_unittest.h | 1 +
src/lib/dns/tests/rrclass_unittest.cc | 10 +-
src/lib/dns/tests/zone_checker_unittest.cc | 352 ++++++
src/lib/dns/zone_checker.cc | 196 +++
src/lib/dns/zone_checker.h | 162 +++
src/lib/log/log_messages.mes | 4 +-
src/lib/log/logger_manager_impl.cc | 10 +
src/lib/python/isc/bind10/component.py | 10 +-
src/lib/python/isc/bind10/special_component.py | 15 +-
src/lib/python/isc/bind10/tests/component_test.py | 32 +-
src/lib/python/isc/datasrc/client_inc.cc | 43 +-
src/lib/python/isc/datasrc/client_python.cc | 32 +
src/lib/python/isc/datasrc/tests/.gitignore | 1 +
src/lib/python/isc/datasrc/tests/datasrc_test.py | 31 +-
src/lib/python/isc/ddns/libddns_messages.mes | 2 +-
src/lib/python/isc/log/log.cc | 2 +-
src/lib/python/isc/log_messages/Makefile.am | 2 +
src/lib/python/isc/log_messages/msgq_messages.py | 1 +
src/lib/resolve/resolve_messages.mes | 6 +-
src/lib/server_common/server_common_messages.mes | 4 +-
src/lib/testutils/dnsmessage_test.h | 3 +-
src/lib/util/encode/base_n.cc | 3 +-
tests/system/bindctl/setup.sh | 2 +-
tests/system/bindctl/tests.sh | 4 +
tests/system/conf.sh.in | 5 +-
tests/system/glue/setup.sh.in | 8 +-
tests/system/ixfr/in-1/setup.sh.in | 2 +-
tests/system/ixfr/in-2/setup.sh.in | 2 +-
tests/system/ixfr/in-3/setup.sh.in | 2 +-
tests/system/ixfr/in-4/setup.sh.in | 2 +-
tests/tools/perfdhcp/perf_pkt4.cc | 2 +-
tests/tools/perfdhcp/test_control.cc | 20 +-
tests/tools/perfdhcp/tests/.gitignore | 5 +
.../tools/perfdhcp/tests/test_control_unittest.cc | 3 -
237 files changed, 15594 insertions(+), 3791 deletions(-)
create mode 100644 m4macros/ax_boost_for_bind10.m4
create mode 100755 src/bin/auth/tests/gen-query-testdata.py
delete mode 100644 src/bin/auth/tests/query_inmemory_unittest.cc
create mode 100644 src/bin/auth/tests/testdata/example-base-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-base.zone.in
create mode 100644 src/bin/auth/tests/testdata/example-common-inc-template.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3.zone.in
delete mode 100644 src/bin/auth/tests/testdata/example.zone
create mode 100644 src/bin/auth/tests/testdata/example.zone.in
create mode 100644 src/bin/msgq/msgq_messages.mes
delete mode 100755 src/bin/msgq/tests/msgq_test.in
create mode 100644 src/lib/dhcp/hwaddr.cc
create mode 100644 src/lib/dhcp/hwaddr.h
create mode 100644 src/lib/dhcp/tests/hwaddr_unittest.cc
copy src/{bin/dhcp6/config_parser.h => lib/dhcpsrv/dhcp_config_parser.h} (55%)
copy src/{bin/dhcp4/dhcp4_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} (86%)
create mode 100644 src/lib/dhcpsrv/dhcpsrv_log.h
create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
create mode 100644 src/lib/dhcpsrv/option_space.cc
create mode 100644 src/lib/dhcpsrv/option_space.h
create mode 100644 src/lib/dhcpsrv/option_space_container.h
create mode 100644 src/lib/dhcpsrv/tests/option_space_unittest.cc
create mode 100644 src/lib/dhcpsrv/tests/test_utils.cc
create mode 100644 src/lib/dhcpsrv/tests/test_utils.h
copy src/lib/{asiolink/io_error.h => dhcpsrv/utils.h} (52%)
delete mode 100644 src/lib/dns/character_string.cc
delete mode 100644 src/lib/dns/character_string.h
copy src/lib/dns/{python/nsec3hash_python.h => dns_fwd.h} (52%)
create mode 100644 src/lib/dns/python/rrset_collection_python.cc
copy src/lib/{python/isc/acl/dns_requestacl_python.h => dns/python/rrset_collection_python.h} (55%)
create mode 100644 src/lib/dns/python/rrset_collection_python_inc.cc
create mode 100644 src/lib/dns/python/tests/rrset_collection_python_test.py
create mode 100644 src/lib/dns/python/tests/zone_checker_python_test.py
create mode 100644 src/lib/dns/python/zone_checker_python.cc
copy src/lib/{python/isc/util/cio/socketsession_python.h => dns/python/zone_checker_python.h} (74%)
create mode 100644 src/lib/dns/python/zone_checker_python_inc.cc
create mode 100644 src/lib/dns/rdata/generic/detail/lexer_util.h
delete mode 100644 src/lib/dns/tests/character_string_unittest.cc
create mode 100644 src/lib/dns/tests/zone_checker_unittest.cc
create mode 100644 src/lib/dns/zone_checker.cc
create mode 100644 src/lib/dns/zone_checker.h
create mode 100644 src/lib/python/isc/log_messages/msgq_messages.py
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index dd6903c..7bc41b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,6 @@ TAGS
/all.info
/coverage-cpp-html
/dns++.pc
-/report.info
+/local.zone.sqlite3
/logger_lockfile
+/report.info
diff --git a/ChangeLog b/ChangeLog
index a418708..e9e2bd3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,130 @@
+546. [func] marcin
+ DHCP option definitions can be now created using the
+ Configuration Manager. The option definition specifies
+ the option code, name and the types of the data being
+ carried by the option. The Configuration Manager
+ reports an error on attempt to override standard DHCP
+ option definition.
+ (Trac #2317, git 71e25eb81e58a695cf3bad465c4254b13a50696e)
+
+545. [func] jinmei
+ libdns++: the SOA Rdata class now uses the generic lexer in
+ constructors from text. This means that the MNAME and RNAME of an
+ SOA RR 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.
+ (Trac #2500, git 019ca218027a218921519f205139b96025df2bb5)
+
+544. [func] tomek
+ b10-dhcp4: Allocation engine support for IPv4 added. Currently
+ supported operations are server selection (Discover/Offer),
+ address assignment (Request/Ack), address renewal (Request/Ack),
+ and address release (Release). Expired leases can be reused.
+ Some options (e.g. Router Option) are still hardcoded, so the
+ DHCPv4 server is not yet usable, although its address allocation
+ is operational.
+ (Trac #2320, git 60606cabb1c9584700b1f642bf2af21a35c64573)
+
+543. [func]* jelte
+ When calling getFullConfig() as a module, , the configuration is now
+ returned as properly-structured JSON. Previously, the structure had
+ been flattened, with all data being labelled by fully-qualified element
+ names.
+ (Trac #2619, git bed3c88c25ea8f7e951317775e99ebce3340ca22)
+
+542. [func] marcin
+ Created OptionSpace and OptionSpace6 classes to represent DHCP
+ option spaces. The option spaces are used to group instances
+ and definitions of options having uniqe codes. A special type
+ of option space is the so-called "vendor specific option space"
+ which groups sub-options sent within Vendor Encapsulated Options.
+ The new classes are not used yet but they will be used once
+ the creation of option spaces by configuration manager is
+ implemented.
+ (Trac #2313, git 37a27e19be874725ea3d560065e5591a845daa89)
+
+541. [func] marcin
+ Added routines to search for configured DHCP options and their
+ definitions using name of the option space they belong to.
+ New routines are called internally from the DHCPv4 and DHCPv6
+ servers code.
+ (Trac #2315, git 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8)
+
+540. [func] marcin
+ DHCP Option values can be now specified using a string of
+ tokens separated with comma sign. Subsequent tokens are used
+ to set values for corresponding data fields in a particular
+ DHCP option. The format of the token matches the data type
+ of the corresponding option field: e.g. "192.168.2.1" for IPv4
+ address, "5" for integer value etc.
+ (Trac #2545, git 792c129a0785c73dd28fd96a8f1439fe6534a3f1)
+
+539. [func] stephen
+ Add logging to the DHCP server library.
+ (Trac #2524, git b55b8b6686cc80eed41793c53d1779f4de3e9e3c)
+
+538. [bug] muks
+ Added escaping of special characters (double-quotes, semicolon,
+ backslash, etc.) in text-like RRType's toText() implementation.
+ Without this change, some TXT and SPF RDATA were incorrectly
+ stored in SQLite3 datasource as they were not escaped.
+ (Trac #2535, git f516fc484544b7e08475947d6945bc87636d4115)
+
+537. [func] tomek
+ b10-dhcp6: Support for RELEASE message has been added. Clients
+ are now able to release their non-temporary IPv6 addresses.
+ (Trac #2326, git 0974318566abe08d0702ddd185156842c6642424)
+
+536. [build] jinmei
+ Detect a build issue on FreeBSD with g++ 4.2 and Boost installed via
+ FreeBSD ports at ./configure time. This seems to be a bug of
+ FreeBSD ports setup and has been reported to the maintainer:
+ http://www.freebsd.org/cgi/query-pr.cgi?pr=174753
+ Until it's fixed, you need to build BIND 10 for FreeBSD that has
+ this problem with specifying --without-werror, with clang++
+ (development version), or with manually extracted Boost header
+ files (no compiled Boost library is necessary).
+ (Trac #1991, git 6b045bcd1f9613e3835551cdebd2616ea8319a36)
+
+535. [bug] jelte
+ The log4cplus internal logging mechanism has been disabled, and no
+ output from the log4cplus library itself should be printed to
+ stderr anymore. This output can be enabled by using the
+ compile-time option --enable-debug.
+ (Trac #1081, git db55f102b30e76b72b134cbd77bd183cd01f95c0)
+
+534. [func]* vorner
+ The b10-msgq now uses the same logging format as the rest
+ of the system. However, it still doesn't obey the common
+ configuration, as due to technical issues it is not able
+ to read it yet.
+ (git 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb)
+
+bind10-1.0.0-beta released on December 20, 2012
+
+533. [build]* jreed
+ Changed the package name in configure.ac from bind10-devel
+ to bind10. This means the default sub-directories for
+ etc, include, libexec, share, share/doc, and var are changed.
+ If upgrading from a previous version, you may need to move
+ and update your configurations or change references for the
+ old locations.
+ (git bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2)
+
+532. [func] marcin
+ Implemented configuration of DHCPv4 option values using
+ the configuration manager. In order to set values for the
+ data fields carried by a particular option, the user
+ specifies a string of hexadecimal digits that is converted
+ to binary data and stored in the option buffer. A more
+ user-friendly way of specifying option content is planned.
+ (Trac #2544, git fed1aab5a0f813c41637807f8c0c5f8830d71942)
+
+531. [func] tomek
+ b10-dhcp6: Added support for expired leases. Leases for IPv6
+ addresses that are past their valid lifetime may be recycled, i.e.
+ rellocated to other clients if needed.
+ (Trac #2327, git 62a23854f619349d319d02c3a385d9bc55442d5e)
+
530. [func]* team
b10-loadzone was fully overhauled. It now uses C++-based zone
parser and loader library, performing stricter checks, having
@@ -13,16 +140,17 @@
(Trac #2380, git 689b015753a9e219bc90af0a0b818ada26cc5968)
529. [func]* team
- The in-memory data source now uses a more complete master file
- parser to load textual zone files. As of this change it supports
- multi-line RR representation and more complete support for escaped
- and quoted strings. It also produces more helpful log when there
- is an error in the zone file. It will be enhanced as more
- specific tasks in the #2368 meta ticket are completed. The new
- parser is generally upper compatible to the previous one, but due
- to the tighter checks some input that has been accepted so far
- could now be rejected, so it's advisable to check if you use
- textual zone files directly loaded to memory.
+ The in-memory data source now uses a more complete master
+ file parser to load textual zone files. As of this change
+ it supports multi-line RR representation and more complete
+ support for escaped and quoted strings. It also produces
+ more helpful log messages when there is an error in the zone
+ file. It will be enhanced as more specific tasks in the
+ #2368 meta ticket are completed. The new parser is generally
+ backward compatible to the previous one, but due to the
+ tighter checks some input that has been accepted so far
+ could now be rejected, so it's advisable to check if you
+ use textual zone files directly loaded to memory.
(Trac #2470, git c4cf36691115c15440b65cac16f1c7fcccc69521)
528. [func] marcin
diff --git a/README b/README
index fdc11a1..306b7d8 100644
--- a/README
+++ b/README
@@ -7,16 +7,20 @@ DHCP. BIND 10 is written in C++ and Python and provides a modular
environment for serving, maintaining, and developing DNS and DHCP.
This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
-backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
-remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
-AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
-secondary manager, b10-stats statistics collection and reporting
-daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
-b10-host DNS lookup utility, and a new libdns++ library for C++
-with a python wrapper. BIND 10 also provides experimental DHCPv4
-and DHCPv6 servers, b10-dhcp4 and b10-dhcp6, a portable DHCP library,
-libdhcp++, and a DHCP benchmarking tool, perfdhcp.
+bus, b10-cmdctl remote control daemon, b10-cfgmgr configuration
+manager, b10-stats statistics collection and reporting daemon, and
+b10-stats-httpd for HTTP access to XML-formatted stats.
+
+For DNS services, it provides the b10-auth authoritative DNS server
+(with SQLite3 and in-memory backends), b10-resolver recursive or
+forwarding DNS server, b10-xfrin IXFR/AXFR inbound service, b10-xfrout
+outgoing IXFR/AXFR service, b10-zonemgr secondary manager, libdns++
+library for C++ with a python wrapper, and many tests and example
+programs.
+
+BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
+b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
+a DHCP benchmarking tool, perfdhcp.
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
diff --git a/configure.ac b/configure.ac
index 24e9b03..892bc6c 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-devel, 20120817, bind10-dev at isc.org)
+AC_INIT(bind10, 20121219, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE([foreign])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
@@ -166,8 +166,6 @@ fi
fi dnl GXX = yes
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
# allow building programs with static link. we need to make it selective
# because loadable modules cannot be statically linked.
AC_ARG_ENABLE([static-link],
@@ -838,57 +836,22 @@ LIBS=$LIBS_SAVED
#
# Configure Boost header path
#
-# If explicitly specified, use it.
-AC_ARG_WITH([boost-include],
- AC_HELP_STRING([--with-boost-include=PATH],
- [specify exact directory for Boost headers]),
- [boost_include_path="$withval"])
-# If not specified, try some common paths.
-if test -z "$with_boost_include"; then
- boostdirs="/usr/local /usr/pkg /opt /opt/local"
- for d in $boostdirs
- do
- if test -f $d/include/boost/shared_ptr.hpp; then
- boost_include_path=$d/include
- break
- fi
- done
+AX_BOOST_FOR_BIND10
+# Boost offset_ptr is required in one library and not optional right now, so
+# we unconditionally fail here if it doesn't work.
+if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+ AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])
fi
-CPPFLAGS_SAVES="$CPPFLAGS"
-if test "${boost_include_path}" ; then
- BOOST_INCLUDES="-I${boost_include_path}"
- CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+
+# There's a known bug in FreeBSD ports for Boost that would trigger a false
+# warning in build with g++ and -Werror (we exclude clang++ explicitly to
+# avoid unexpected false positives).
+if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGPP = "no"; then
+ AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
- AC_MSG_ERROR([Missing required header files.]))
-
-# Detect whether Boost tries to use threads by default, and, if not,
-# make it sure explicitly. In some systems the automatic detection
-# may depend on preceding header files, and if inconsistency happens
-# it could lead to a critical disruption.
-AC_MSG_CHECKING([whether Boost tries to use threads])
-AC_TRY_COMPILE([
-#include <boost/config.hpp>
-#ifdef BOOST_HAS_THREADS
-#error "boost will use threads"
-#endif],,
-[AC_MSG_RESULT(no)
- CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
-[AC_MSG_RESULT(yes)])
-
-# Boost offset_ptr is required in one library (not optional right now), and
-# it's known it doesn't compile on some platforms, depending on boost version,
-# its local configuration, and compiler.
-AC_MSG_CHECKING([Boost offset_ptr compiles])
-AC_TRY_COMPILE([
-#include <boost/interprocess/offset_ptr.hpp>
-],,
-[AC_MSG_RESULT(yes)],
-[AC_MSG_RESULT(no)
- AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])])
-CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
-AC_SUBST(BOOST_INCLUDES)
+# Add some default CPP flags needed for Boost, identified by the AX macro.
+CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
# I can't get some of the #include <asio.hpp> right without this
# TODO: find the real cause of asio/boost wanting pthreads
@@ -1354,10 +1317,12 @@ AC_OUTPUT([doc/version.ent
src/bin/usermgr/run_b10-cmdctl-usermgr.sh
src/bin/usermgr/b10-cmdctl-usermgr.py
src/bin/msgq/msgq.py
- src/bin/msgq/tests/msgq_test
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
src/bin/auth/spec_config.h.pre
+ src/bin/auth/tests/testdata/example.zone
+ src/bin/auth/tests/testdata/example-base.zone
+ src/bin/auth/tests/testdata/example-nsec3.zone
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/tests/process_rename_test.py
@@ -1420,7 +1385,6 @@ AC_OUTPUT([doc/version.ent
chmod +x src/bin/sysinfo/run_sysinfo.sh
chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
chmod +x src/bin/msgq/run_msgq.sh
- chmod +x src/bin/msgq/tests/msgq_test
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/log/tests/console_test.sh
chmod +x src/lib/log/tests/destination_test.sh
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 336cc9b..f1f5859 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -503,7 +503,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>etc/bind10-devel/</filename> —
+ <filename>etc/bind10/</filename> —
configuration files.
</simpara>
</listitem>
@@ -515,7 +515,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>libexec/bind10-devel/</filename> —
+ <filename>libexec/bind10/</filename> —
executables that a user wouldn't normally run directly and
are not run independently.
These are the BIND 10 modules which are daemons started by
@@ -530,13 +530,13 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>share/bind10-devel/</filename> —
+ <filename>share/bind10/</filename> —
configuration specifications.
</simpara>
</listitem>
<listitem>
<simpara>
- <filename>share/doc/bind10-devel/</filename> —
+ <filename>share/doc/bind10/</filename> —
this guide and other supplementary documentation.
</simpara>
</listitem>
@@ -548,7 +548,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>var/bind10-devel/</filename> —
+ <filename>var/bind10/</filename> —
data source and configuration databases.
</simpara>
</listitem>
@@ -910,7 +910,7 @@ as a dependency earlier -->
Administrators do not communicate directly with the
<command>b10-msgq</command> daemon.
By default, BIND 10 uses a UNIX domain socket file named
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>
+ <filename>/usr/local/var/bind10/msg_socket</filename>
for this interprocess communication.
</para>
@@ -972,7 +972,7 @@ config changes are actually commands to cfgmgr
<!-- TODO: what about command line switch to change this? -->
<para>
The stored configuration file is at
- <filename>/usr/local/var/bind10-devel/b10-config.db</filename>.
+ <filename>/usr/local/var/bind10/b10-config.db</filename>.
(The directory is what was defined at build configure time for
<option>--localstatedir</option>.
The default is <filename>/usr/local/var/</filename>.)
@@ -1065,13 +1065,13 @@ but you might wanna check with likun
<para>The HTTPS server requires a private key,
such as a RSA PRIVATE KEY.
The default location is at
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
(A sample key is at
- <filename>/usr/local/share/bind10-devel/cmdctl-keyfile.pem</filename>.)
+ <filename>/usr/local/share/bind10/cmdctl-keyfile.pem</filename>.)
It also uses a certificate located at
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
(A sample certificate is at
- <filename>/usr/local/share/bind10-devel/cmdctl-certfile.pem</filename>.)
+ <filename>/usr/local/share/bind10/cmdctl-certfile.pem</filename>.)
This may be a self-signed certificate or purchased from a
certification authority.
</para>
@@ -1107,11 +1107,11 @@ but that is a single file, maybe this should go back to that format?
<para>
The <command>b10-cmdctl</command> daemon also requires
the user account file located at
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
This comma-delimited file lists the accounts with a user name,
hashed password, and salt.
(A sample file is at
- <filename>/usr/local/share/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/share/bind10/cmdctl-accounts.csv</filename>.
It contains the user named <quote>root</quote> with the password
<quote>bind10</quote>.)
</para>
@@ -1141,14 +1141,14 @@ or accounts database -->
The configuration items for <command>b10-cmdctl</command> are:
<varname>accounts_file</varname> which defines the path to the
user accounts database (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>);
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>);
<varname>cert_file</varname> which defines the path to the
PEM certificate file (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>);
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>);
and
<varname>key_file</varname> which defines the path to the
PEM private key file (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>).
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>).
</para>
</section>
@@ -1872,7 +1872,7 @@ tsig_keys/keys[0] "example.key.:c2VjcmV0" string (modified)
Each rule is a name-value mapping (a dictionary, in the JSON
terminology). Each rule must contain exactly one mapping called
"action", which describes what should happen if the rule applies.
- There may be more mappings, calld matches, which describe the
+ There may be more mappings, called matches, which describe the
conditions under which the rule applies.
</para>
@@ -2459,7 +2459,7 @@ can use various data source backends.
data source — one that serves things like
<quote>AUTHORS.BIND.</quote>. The IN class contains single SQLite3
data source with database file located at
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
</para>
<para>
@@ -3343,23 +3343,21 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
<note>
<para>
- As of November 2012, the DHCPv4 component is a
- skeleton server. That means that while it is capable of
- performing DHCP configuration, it is not fully functional.
- In particular, it does not have a functional lease
- database. This means that they will assign the same, fixed,
- hardcoded addresses to any client that will ask. See <xref
- linkend="dhcp4-limit"/> for a
- detailed description.
+ As of January 2013, the DHCPv4 component is a work in progress.
+ That means that while it is capable of performing DHCP configuration,
+ it is not fully functional. The server is able to offer,
+ assign, renew, release and reuse expired leases, but some of the
+ options are not configurable yet. In particular Router option is hardcoded.
+ This means that the server is not really usable in actual deployments
+ yet. See <xref linkend="dhcp4-limit"/> for a detailed description.
</para>
</note>
<section id="dhcp4-usage">
<title>DHCPv4 Server Usage</title>
<para>BIND 10 has provided the DHCPv4 server component since December
- 2011. It is a skeleton server and can be described as an early
- prototype that is not fully functional yet. It is mature enough
- to conduct first tests in lab environment, but it has
+ 2011. It is current experimental implementation and is not fully functional
+ yet. It is mature enough to conduct tests in lab environment, but it has
significant limitations. See <xref linkend="dhcp4-limit"/> for
details.
</para>
@@ -3480,9 +3478,10 @@ Dhcp4/subnet4 [] list (default)</screen>
</para>
<para>
- Note: Although configuration is now accepted, it is not internally used
- by they server yet. At this stage of development, the only way to alter
- server configuration is to modify its source code. To do so, please edit
+ Note: Although configuration is now accepted, some parts of it is not internally used
+ by they server yet. Address pools are used, but option definitons are not.
+ The only way to alter some options (e.g. Router Option or DNS servers and Domain name)
+ is to modify source code. To do so, please edit
src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
recompile:
<screen>
@@ -3505,7 +3504,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
<itemizedlist>
<listitem>
<simpara>RFC2131: Supported messages are DISCOVER, OFFER,
- REQUEST, and ACK.</simpara>
+ REQUEST, ACK, NAK, RELEASE.</simpara>
</listitem>
<listitem>
<simpara>RFC2132: Supported options are: PAD (0),
@@ -3513,6 +3512,10 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
Domain Name (15), DNS Servers (6), IP Address Lease Time
(51), Subnet mask (1), and Routers (3).</simpara>
</listitem>
+ <listitem>
+ <simpara>RFC6842: Server responses include client-id option
+ if client sent it in its message.</simpara>
+ </listitem>
</itemizedlist>
</section>
@@ -3533,20 +3536,6 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
communication).</simpara>
</listitem>
<listitem>
- <simpara><command>b10-dhcp4</command> provides a single,
- fixed, hardcoded lease to any client that asks. There is
- no lease manager implemented. If two clients request
- addresses, they will both get the same fixed
- address.</simpara>
- </listitem>
- <listitem>
- <simpara><command>b10-dhcp4</command> does not support any
- configuration mechanisms yet. The whole configuration is
- currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see <xref
- linkend="dhcp4-config"/> for details.</simpara>
- </listitem>
- <listitem>
<simpara>Upon start, the server will open sockets on all
interfaces that are not loopback, are up and running and
have IPv4 address.</simpara>
@@ -3574,9 +3563,9 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
sending ICMP echo request.</simpara>
</listitem>
<listitem>
- <simpara>Address renewal (RENEW), rebinding (REBIND),
- confirmation (CONFIRM), duplication report (DECLINE) and
- release (RELEASE) are not supported yet.</simpara>
+ <simpara>Address rebinding (REQUEST/Rebinding), confirmation
+ (CONFIRM) and duplication report (DECLINE) are not supported
+ yet.</simpara>
</listitem>
<listitem>
<simpara>DNS Update is not supported yet.</simpara>
@@ -3673,7 +3662,7 @@ mysql></screen>
<para>
3. Create the database tables:
<screen>mysql> <userinput>CONNECT kea;</userinput>
-mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10-devel/dhcpdb_create.mysql</userinput></screen>
+mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10/dhcpdb_create.mysql</userinput></screen>
</para>
<para>
4. Create the user under which BIND 10 will access the database and grant it access to the database tables:
@@ -3871,7 +3860,7 @@ Dhcp6/subnet6 [] list (default)</screen>
<section id="dhcp6-limit">
<title>DHCPv6 Server Limitations</title>
<para> These are the current limitations and known problems
- with the the DHCPv6 server
+ with the DHCPv6 server
software. Most of them are reflections of the early stage of
development and should be treated as <quote>not implemented
yet</quote>, rather than actual limitations.</para>
@@ -4156,7 +4145,7 @@ specify module-wide logging and see what appears...
If there are multiple logger specifications in the
configuration that might match a particular logger, the
specification with the more specific logger name takes
- precedence. For example, if there are entries for for
+ precedence. For example, if there are entries for
both <quote>*</quote> and <quote>Resolver</quote>, the
resolver module — and all libraries it uses —
will log messages according to the configuration in the
diff --git a/examples/README b/examples/README
index 08f53fa..aa5f3c9 100644
--- a/examples/README
+++ b/examples/README
@@ -15,7 +15,7 @@ the "m4" subdirectory as a template for your own project. The key is
to call the AX_ISC_BIND10 function (as the sample configure.ac does)
from your configure.ac. Then it will check the availability of
necessary stuff and set some corresponding AC variables. You can then
-use the resulting variables in your Makefile.in or Makefile.ac.
+use the resulting variables in your Makefile.in or Makefile.am.
If you use automake, don't forget adding the following line to the top
level Makefile.am:
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
new file mode 100644
index 0000000..1ce367e
--- /dev/null
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -0,0 +1,112 @@
+dnl @synopsis AX_BOOST_FOR_BIND10
+dnl
+dnl Test for the Boost C++ header files intended to be used within BIND 10
+dnl
+dnl If no path to the installed boost header files is given via the
+dnl --with-boost-include option, the macro searchs under
+dnl /usr/local /usr/pkg /opt /opt/local directories.
+dnl If it cannot detect any workable path for Boost, this macro treats it
+dnl as a fatal error (so it cannot be called if the availability of Boost
+dnl is optional).
+dnl
+dnl This macro also tries to identify some known portability issues, and
+dnl sets corresponding variables so the caller can react to (or ignore,
+dnl depending on other configuration) specific issues appropriately.
+dnl
+dnl This macro calls:
+dnl
+dnl AC_SUBST(BOOST_INCLUDES)
+dnl
+dnl And possibly sets:
+dnl CPPFLAGS_BOOST_THREADCONF should be added to CPPFLAGS by caller
+dnl BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
+dnl error; otherwise set to "no"
+dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
+dnl build error; otherwise set to "no"
+dnl
+
+AC_DEFUN([AX_BOOST_FOR_BIND10], [
+AC_LANG_SAVE
+AC_LANG([C++])
+
+#
+# Configure Boost header path
+#
+# If explicitly specified, use it.
+AC_ARG_WITH([boost-include],
+ AC_HELP_STRING([--with-boost-include=PATH],
+ [specify exact directory for Boost headers]),
+ [boost_include_path="$withval"])
+# If not specified, try some common paths.
+if test -z "$with_boost_include"; then
+ boostdirs="/usr/local /usr/pkg /opt /opt/local"
+ for d in $boostdirs
+ do
+ if test -f $d/include/boost/shared_ptr.hpp; then
+ boost_include_path=$d/include
+ break
+ fi
+ done
+fi
+
+# Check the path with some specific headers.
+CPPFLAGS_SAVED="$CPPFLAGS"
+if test "${boost_include_path}" ; then
+ BOOST_INCLUDES="-I${boost_include_path}"
+ CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+fi
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
+ AC_MSG_ERROR([Missing required header files.]))
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly. In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+# Boost offset_ptr is known to not compile on some platforms, depending on
+# boost version, its local configuration, and compiler. Detect it.
+AC_MSG_CHECKING([Boost offset_ptr compiles])
+AC_TRY_COMPILE([
+#include <boost/interprocess/offset_ptr.hpp>
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_OFFSET_PTR_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_OFFSET_PTR_WOULDFAIL=yes])
+
+# Detect build failure case known to happen with Boost installed via
+# FreeBSD ports
+if test "X$GXX" = "Xyes"; then
+ CXXFLAGS_SAVED="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -Werror"
+
+ AC_MSG_CHECKING([Boost numeric_cast compiles with -Werror])
+ AC_TRY_COMPILE([
+ #include <boost/numeric/conversion/cast.hpp>
+ ],[
+ return (boost::numeric_cast<short>(0));
+ ],[AC_MSG_RESULT(yes)
+ BOOST_NUMERIC_CAST_WOULDFAIL=no],
+ [AC_MSG_RESULT(no)
+ BOOST_NUMERIC_CAST_WOULDFAIL=yes])
+
+ CXXFLAGS="$CXXFLAGS_SAVED"
+else
+ # This doesn't matter for non-g++
+ BOOST_NUMERIC_CAST_WOULDFAIL=no
+fi
+
+AC_SUBST(BOOST_INCLUDES)
+
+CPPFLAGS="$CPPFLAGS_SAVED"
+AC_LANG_RESTORE
+])dnl AX_BOOST_FOR_BIND10
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index ac9ffb9..d93da51 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -141,7 +141,7 @@ The specific problem is printed in the log message.
The thread for maintaining data source clients has received a command to
reconfigure, and has now started this process.
-% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed successfully
The thread for maintaining data source clients has finished reconfiguring
the data source clients, and is now running with the new configuration.
@@ -169,7 +169,7 @@ probably better to stop and restart it.
% AUTH_DATA_SOURCE data source database file: %1
This is a debug message produced by the authoritative server when it accesses a
-datebase data source, listing the file that is being accessed.
+database data source, listing the file that is being accessed.
% AUTH_DNS_SERVICES_CREATED DNS services created
This is a debug message indicating that the component that will handling
@@ -184,7 +184,7 @@ reason for the failure is given in the message.) The server will drop the
packet.
% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
+An error was encountered when the authoritative server specified
statistics data which is invalid for the auth specification file.
% AUTH_LOAD_TSIG loading TSIG keys
@@ -208,7 +208,7 @@ requests to b10-ddns) to handle it, but it failed. The authoritative
server returns SERVFAIL to the client on behalf of the separate
process. The error could be configuration mismatch between b10-auth
and the recipient component, or it may be because the requests are
-coming too fast and the receipient process cannot keep up with the
+coming too fast and the recipient process cannot keep up with the
rate, or some system level failure. In either case this means the
BIND 10 system is not working as expected, so the administrator should
look into the cause and address the issue. The log message includes
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index b34009d..88f80e2 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>June 20, 2012</date>
+ <date>December 18, 2012</date>
</refentryinfo>
<refmeta>
@@ -100,7 +100,7 @@
<varname>database_file</varname> defines the path to the
SQLite3 zone file when using the sqlite datasource.
The default is
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
</para>
<para>
@@ -157,6 +157,7 @@
incoming TCP connections, in milliseconds. If the query
is not sent within this time, the connection is closed.
Setting this to 0 will disable TCP timeouts completely.
+ The default is 5000 (five seconds).
</para>
<!-- TODO: formating -->
@@ -165,6 +166,15 @@
</para>
<para>
+ <command>getstats</command> tells <command>b10-auth</command>
+ to report its defined statistics data in JSON format.
+ It will not report about unused counters.
+ This is used by the
+ <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry> daemon.
+ (The <command>sendstats</command> command is deprecated.)
+ </para>
+
+ <para>
<command>loadzone</command> tells <command>b10-auth</command>
to load or reload a zone file. The arguments include:
<varname>class</varname> which optionally defines the class
@@ -181,13 +191,6 @@
</para>
<para>
- <command>sendstats</command> tells <command>b10-auth</command>
- to send its statistics data to
- <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- immediately.
- </para>
-
- <para>
<command>shutdown</command> exits <command>b10-auth</command>.
This has an optional <varname>pid</varname> argument to
select the process ID to stop.
@@ -195,6 +198,28 @@
if configured.)
</para>
+ <para>
+ <command>start_ddns_forwarder</command> starts (or restarts) the
+ internal forwarding of DDNS Update messages.
+ This is used by the
+ <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon to tell <command>b10-auth</command> that DDNS Update
+ messages can be forwarded.
+ <note><simpara>This is not expected to be called by administrators;
+ it will be removed as a public command in the future.</simpara></note>
+ </para>
+
+ <para>
+ <command>stop_ddns_forwarder</command> stops the internal
+ forwarding of DDNS Update messages.
+ This is used by the
+ <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon to tell <command>b10-auth</command> that DDNS Update
+ messages should not be forwarded.
+ <note><simpara>This is not expected to be called by administrators;
+ it will be removed as a public command in the future.</simpara></note>
+ </para>
+
</refsect1>
<refsect1>
@@ -230,7 +255,7 @@
<refsect1>
<title>FILES</title>
<para>
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>
— Location for the SQLite3 zone database
when <emphasis>database_file</emphasis> configuration is not
defined.
@@ -244,6 +269,9 @@
<refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
+ <refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
<refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
index d6d1ec8..a45eff7 100644
--- a/src/bin/auth/tests/.gitignore
+++ b/src/bin/auth/tests/.gitignore
@@ -1 +1,3 @@
/run_unittests
+/example_base_inc.cc
+/example_nsec3_inc.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 42f202b..36a3c68 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_builddir)/src/bin/cfgmgr/plugins\"
@@ -50,7 +51,6 @@ run_unittests_SOURCES += config_syntax_unittest.cc
run_unittests_SOURCES += command_unittest.cc
run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += query_inmemory_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
@@ -81,6 +81,40 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+# The following are definitions for auto-generating test data for query
+# tests.
+BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += testdata/example-base.sqlite3
+BUILT_SOURCES += testdata/example-nsec3.sqlite3
+
+EXTRA_DIST = gen-query-testdata.py
+
+CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
+
+example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-base-inc.zone example_base_inc.cc
+
+example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
+
+testdata/example-base.sqlite3: testdata/example-base.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
+ example.com testdata/example-base.zone
+
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
+ example.com testdata/example-nsec3.zone
+
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/gen-query-testdata.py b/src/bin/auth/tests/gen-query-testdata.py
new file mode 100755
index 0000000..a71deb0
--- /dev/null
+++ b/src/bin/auth/tests/gen-query-testdata.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This is a supplemental script to auto generate test data in the form of
+C++ source code from a DNS zone file.
+
+Usage: python gen-query-testdata.py source_file output-cc-file
+
+The usage doesn't matter much, though, because it's expected to be invoked
+from Makefile, and that would be only use case of this script.
+"""
+
+import sys
+import re
+
+# Markup for variable definition
+re_start_rr = re.compile('^;var=(.*)')
+
+# Skip lines starting with ';' (comments) or empty lines. re_start_rr
+# will also match this expression, so it should be checked first.
+re_skip = re.compile('(^;)|(^\s*$)')
+
+def parse_input(input_file):
+ '''Build an internal list of RR data from the input source file.
+
+ It generates a list of (variable_name, list of RR) tuples, where
+ variable_name is the expected C++ variable name for the subsequent RRs
+ if they are expected to be named. It can be an empty string if the RRs
+ are only expected to appear in the zone file.
+ The second element of the tuple is a list of strings, each of which
+ represents a single RR, e.g., "example.com 3600 IN A 192.0.2.1".
+
+ '''
+ result = []
+ rrs = None
+ with open(input_file) as f:
+ for line in f:
+ m = re_start_rr.match(line)
+ if m:
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+ rrs = []
+ rr_varname = m.group(1)
+ elif re_skip.match(line):
+ continue
+ else:
+ rrs.append(line.rstrip('\n'))
+
+ # if needed, store the last RRs (they are not followed by 'var=' mark)
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+
+ return result
+
+def generate_variables(out_file, rrsets_data):
+ '''Generate a C++ source file containing a C-string variables for RRs.
+
+ This produces a definition of C-string for each RRset that is expected
+ to be named as follows:
+ const char* const var_name =
+ "example.com. 3600 IN A 192.0.2.1\n"
+ "example.com. 3600 IN A 192.0.2.2\n";
+
+ Escape character '\' in the string will be further escaped so it will
+ compile.
+
+ '''
+ with open(out_file, 'w') as out:
+ for (var_name, rrs) in rrsets_data:
+ if len(var_name) > 0:
+ out.write('const char* const ' + var_name + ' =\n')
+ # Combine all RRs, escaping '\' as a C-string
+ out.write('\n'.join([' \"%s\\n\"' %
+ (rr.replace('\\', '\\\\'))
+ for rr in rrs]))
+ out.write(';\n')
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ sys.stderr.write('gen-query-testdata.py require 2 args\n')
+ sys.exit(1)
+ rrsets_data = parse_input(sys.argv[1])
+ generate_variables(sys.argv[2], rrsets_data)
+
diff --git a/src/bin/auth/tests/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc
deleted file mode 100644
index f587ba2..0000000
--- a/src/bin/auth/tests/query_inmemory_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dns/name.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <cc/data.h>
-
-#include <datasrc/client_list.h>
-
-#include <auth/query.h>
-
-#include <testutils/dnsmessage_test.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::testutils;
-using isc::datasrc::ConfigurableClientList;
-using std::string;
-
-namespace {
-
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// This is not inside the zone, this is created at runtime
-const char* const synthetized_cname_txt =
- "www.dname.example.com. 3600 IN CNAME "
- "www.somethinglong.dnametarget.example.com.\n";
-
-// This is a subset of QueryTest using (subset of) the same test data, but
-// with the production in-memory data source. Both tests should be eventually
-// unified to avoid duplicates.
-class InMemoryQueryTest : public ::testing::Test {
-protected:
- InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) {
- response.setRcode(Rcode::NOERROR());
- response.setOpcode(Opcode::QUERY());
- list.configure(isc::data::Element::fromJSON(
- "[{\"type\": \"MasterFiles\","
- " \"cache-enable\": true, "
- " \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_DIR "/example.zone") +
- "\"}}]"), true);
- }
-
- ConfigurableClientList list;
- Message response;
- Query query;
-};
-
-// A wrapper to check resulting response message commonly used in
-// tests below.
-// check_origin needs to be specified only when the authority section has
-// an SOA RR. The interface is not generic enough but should be okay
-// for our test cases in practice.
-void
-responseCheck(Message& response, const isc::dns::Rcode& rcode,
- unsigned int flags, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount,
- const char* const expected_answer,
- const char* const expected_authority,
- const char* const expected_additional,
- const Name& check_origin = Name::ROOT_NAME())
-{
- // In our test cases QID, Opcode, and QDCOUNT should be constant, so
- // we don't bother the test cases specifying these values.
- headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
- flags, 0, ancount, nscount, arcount);
- if (expected_answer != NULL) {
- rrsetsCheck(expected_answer,
- response.beginSection(Message::SECTION_ANSWER),
- response.endSection(Message::SECTION_ANSWER),
- check_origin);
- }
- if (expected_authority != NULL) {
- rrsetsCheck(expected_authority,
- response.beginSection(Message::SECTION_AUTHORITY),
- response.endSection(Message::SECTION_AUTHORITY),
- check_origin);
- }
- if (expected_additional != NULL) {
- rrsetsCheck(expected_additional,
- response.beginSection(Message::SECTION_ADDITIONAL),
- response.endSection(Message::SECTION_ADDITIONAL));
- }
-}
-
-/*
- * Test a query under a domain with DNAME. We should get a synthetized CNAME
- * as well as the DNAME.
- *
- * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
- * as well. This includes tests pointing inside the zone, outside the zone,
- * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
- */
-TEST_F(InMemoryQueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
- response);
-
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
- (string(dname_txt) + synthetized_cname_txt).c_str(),
- NULL, NULL);
-}
-}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 84b7f8a..a22d2d7 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <map>
-#include <sstream>
-#include <vector>
-
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/static_assert.hpp>
@@ -24,9 +20,12 @@
#include <dns/masterload.h>
#include <dns/message.h>
+#include <dns/master_loader.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
+#include <dns/rrcollator.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
#include <dns/rdataclass.h>
@@ -40,6 +39,12 @@
#include <gtest/gtest.h>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -49,7 +54,7 @@ using namespace isc::testutils;
namespace {
-// Simple wrapper for a sincle data source client.
+// Simple wrapper for a single data source client.
// The list simply delegates all the answers to the single
// client.
class SingletonList : public ClientList {
@@ -79,150 +84,21 @@ private:
DataSourceClient& client_;
};
+// These are commonly used data (auto-generated). There are some exceptional
+// data that are only used in a limited scenario, which are defined separately
+// below.
+#include <auth/tests/example_base_inc.cc>
+#include <auth/tests/example_nsec3_inc.cc>
-// This is the content of the mock zone (see below).
-// It's a sequence of textual RRs that is supposed to be parsed by
-// dns::masterLoad(). Some of the RRs are also used as the expected
-// data in specific tests, in which case they are referenced via specific
-// local variables (such as soa_txt).
-//
-// For readability consistency, all strings are placed in a separate line,
-// even if they are very short and can reasonably fit in a single line with
-// the corresponding variable. For example, we write
-// const char* const foo_txt =
-// "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-// instead of
-// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-const char* const soa_txt =
- "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
-const char* const zone_ns_txt =
- "example.com. 3600 IN NS glue.delegation.example.com.\n"
- "example.com. 3600 IN NS noglue.example.com.\n"
- "example.com. 3600 IN NS example.net.\n";
+// This is used only in one pathological test case.
const char* const zone_ds_txt =
"example.com. 3600 IN DS 57855 5 1 "
"B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const ns_addrs_txt =
- "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
- "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
- "noglue.example.com. 3600 IN A 192.0.2.53\n";
-const char* const delegation_txt =
- "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
- "delegation.example.com. 3600 IN NS noglue.example.com.\n"
- "delegation.example.com. 3600 IN NS cname.example.com.\n"
- "delegation.example.com. 3600 IN NS example.org.\n";
-// Borrowed from the RFC4035
-const char* const delegation_ds_txt =
- "delegation.example.com. 3600 IN DS 57855 5 1 "
- "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const mx_txt =
- "mx.example.com. 3600 IN MX 10 www.example.com.\n"
- "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
- "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt =
- "www.example.com. 3600 IN A 192.0.2.80\n";
-const char* const cname_txt =
- "cname.example.com. 3600 IN CNAME www.example.com.\n";
-const char* const cname_nxdom_txt =
- "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
-// CNAME Leading out of zone
-const char* const cname_out_txt =
- "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// Some data at the dname node (allowed by RFC 2672)
-const char* const dname_a_txt =
- "dname.example.com. 3600 IN A 192.0.2.5\n";
+
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
"www.dname.example.com. 3600 IN CNAME "
"www.somethinglong.dnametarget.example.com.\n";
-// The rest of data won't be referenced from the test cases.
-const char* const other_zone_rrs =
- "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
- "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
- "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
-// Wildcards
-const char* const wild_txt =
- "*.wild.example.com. 3600 IN A 192.0.2.7\n";
-const char* const nsec_wild_txt =
- "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
-const char* const cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN NSEC "
- "delegation.example.com. CNAME NSEC RRSIG\n";
-// Wildcard_nxrrset
-const char* const wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
-const char* const nsec_wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
-const char* const wild_txt_next =
- "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
-const char* const nsec_wild_txt_next =
- "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
-// Wildcard empty
-const char* const empty_txt =
- "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
-const char* const nsec_empty_txt =
- "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt =
- "t.example.com. 3600 IN A 192.0.2.15\n";
-const char* const nsec_empty_prev_txt =
- "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
-// Used in NXDOMAIN proof test. We are going to test some unusual case where
-// the best possible wildcard is below the "next domain" of the NSEC RR that
-// proves the NXDOMAIN, i.e.,
-// mx.example.com. (exist)
-// (.no.example.com. (qname, NXDOMAIN)
-// ).no.example.com. (exist)
-// *.no.example.com. (best possible wildcard, not exist)
-const char* const no_txt =
- ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
-// NSEC records.
-const char* const nsec_apex_txt =
- "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
-const char* const nsec_mx_txt =
- "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
-const char* const nsec_no_txt =
- ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
-// We'll also test the case where a single NSEC proves both NXDOMAIN and the
-// non existence of wildcard. The following records will be used for that
-// test.
-// ).no.example.com. (exist, whose NSEC proves everything)
-// *.no.example.com. (best possible wildcard, not exist)
-// nx.no.example.com. (NXDOMAIN)
-// nz.no.example.com. (exist)
-const char* const nz_txt =
- "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
-const char* const nsec_nz_txt =
- "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
-const char* const nsec_nxdomain_txt =
- "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
-
-// NSEC for the normal NXRRSET case
-const char* const nsec_www_txt =
- "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
-
-// Authoritative data without NSEC
-const char* const nonsec_a_txt =
- "nonsec.example.com. 3600 IN A 192.0.2.0\n";
-
-// NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-const string nsec3_apex_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
-const string nsec3_apex_rrsig_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
-const string nsec3_www_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-const string nsec3_www_rrsig_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
// NSEC3 for wild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
@@ -246,42 +122,13 @@ const char* const nsec3_uwild_txt =
"t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-// (Secure) delegation data; Delegation with DS record
-const char* const signed_delegation_txt =
- "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const signed_delegation_ds_txt =
- "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
- "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
-
// (Secure) delegation data; Delegation without DS record (and both NSEC
// and NSEC3 denying its existence)
-const char* const unsigned_delegation_txt =
- "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_nsec_txt =
- "unsigned-delegation.example.com. 3600 IN NSEC "
- "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
// This one will be added on demand
const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
-// Delegation without DS record, and no direct matching NSEC3 record
-const char* const unsigned_delegation_optout_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_optout_nsec_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NSEC "
- "*.uwild.example.com. NS RRSIG NSEC\n";
-
-// (Secure) delegation data; Delegation where the DS lookup will raise an
-// exception.
-const char* const bad_delegation_txt =
- "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
-
-// Delegation from an unsigned parent. There's no DS, and there's no NSEC
-// or NSEC3 that proves it.
-const char* const nosec_delegation_txt =
- "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
-
// 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
@@ -314,58 +161,14 @@ textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
return (rrset);
}
-// This is a mock Zone Finder class for testing.
-// It is a derived class of ZoneFinder for the convenient of tests.
-// Its find() method emulates the common behavior of protocol compliant
-// ZoneFinder classes, but simplifies some minor cases and also supports broken
-// behavior.
-// For simplicity, most names are assumed to be "in zone"; delegations
-// to child zones are identified by the existence of non origin NS records.
-// Another special name is "dname.example.com". Query names under this name
-// will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
-class MockZoneFinder : public ZoneFinder {
+// Setup for faked NSEC3 hash used throughout this test.
+class TestNSEC3Hash : public NSEC3Hash {
+private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap hash_map_;
public:
- MockZoneFinder() :
- origin_(Name("example.com")),
- bad_signed_delegation_name_("bad-delegation.example.com"),
- dname_name_("dname.example.com"),
- has_SOA_(true),
- has_apex_NS_(true),
- rrclass_(RRClass::IN()),
- include_rrsig_anyway_(false),
- use_nsec3_(false),
- nsec_name_(origin_),
- nsec3_fake_(NULL),
- nsec3_name_(NULL)
- {
- stringstream zone_stream;
- zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
- cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
- dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
- nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
- nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
- wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
- wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
- nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
- empty_prev_txt << nsec_empty_prev_txt <<
- nsec3_apex_txt << nsec3_www_txt <<
- signed_delegation_txt << signed_delegation_ds_txt <<
- unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
- unsigned_delegation_optout_txt <<
- unsigned_delegation_optout_nsec_txt <<
- bad_delegation_txt << nosec_delegation_txt;
-
- masterLoad(zone_stream, origin_, rrclass_,
- boost::bind(&MockZoneFinder::loadRRset, this, _1));
-
- empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
- RRClass::IN(),
- RRType::NSEC(),
- RRTTL(3600)));
-
+ TestNSEC3Hash() {
// (Faked) NSEC3 hash map. For convenience we use hardcoded built-in
// map instead of calculating and using actual hash.
// The used hash values are borrowed from RFC5155 examples (they are
@@ -411,6 +214,79 @@ public:
hash_map_[Name("www1.uwild.example.com")] =
"q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = hash_map_.find(name);
+ if (found != hash_map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const rdata::generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+public:
+ TestNSEC3HashCreator() {}
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(uint8_t, uint16_t, const uint8_t*, size_t) const {
+ return (new TestNSEC3Hash);
+ }
+};
+
+// This is a mock Zone Finder class for testing.
+// It is a derived class of ZoneFinder for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// ZoneFinder classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZoneFinder : public ZoneFinder {
+public:
+ MockZoneFinder() :
+ origin_(Name("example.com")),
+ bad_signed_delegation_name_("bad-delegation.example.com"),
+ dname_name_("dname.example.com"),
+ has_SOA_(true),
+ has_apex_NS_(true),
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false),
+ use_nsec3_(false),
+ nsec_name_(origin_),
+ nsec3_fake_(NULL),
+ nsec3_name_(NULL)
+ {
+ RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
+ MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
+ origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator.getCallback());
+ loader.load();
+
+ empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
+ RRClass::IN(),
+ RRType::NSEC(),
+ RRTTL(3600)));
+ }
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
@@ -513,6 +389,19 @@ private:
};
void loadRRset(RRsetPtr rrset) {
+ // For simplicity we dynamically generate RRSIGs and add them below.
+ // The RRSIG RDATA should be consistent with that defined in the
+ // zone file.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return;
+ }
+
+ // NSEC3PARAM is not used in the mock data source (and it would confuse
+ // non-NSEC3 test cases).
+ if (rrset->getType() == RRType::NSEC3PARAM()) {
+ return;
+ }
+
if (rrset->getType() == RRType::NSEC3()) {
// NSEC3 should go to the dedicated table
nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
@@ -565,9 +454,7 @@ private:
// Enabled when not NULL
const FindNSEC3Result* nsec3_fake_;
const Name* nsec3_name_;
-public:
- // Public, to allow tests looking up the right names for something
- map<Name, string> hash_map_;
+ TestNSEC3Hash nsec3_hash_;
};
// A helper function that generates a new RRset based on "wild_rrset",
@@ -627,11 +514,7 @@ MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
// For brevity, we assume several things below: maps should have an
// expected entry when operator[] is used; maps are not empty.
for (int i = 0; i < labels; ++i) {
- const string hlabel = hash_map_[name.split(i, labels - i)];
- if (hlabel.empty()) {
- isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
- name.split(i, labels - i));
- }
+ const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
const Name hname = Name(hlabel + ".example.com");
// We don't use const_iterator so that we can use operator[] below
Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
@@ -893,10 +776,55 @@ MockZoneFinder::find(const Name& name, const RRType& type,
return (createContext(options,NXDOMAIN, RRsetPtr()));
}
-class QueryTest : public ::testing::Test {
+enum DataSrcType {
+ MOCK,
+ INMEMORY,
+ SQLITE3
+};
+
+boost::shared_ptr<ClientList>
+createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
+ boost::shared_ptr<ConfigurableClientList> list;
+ switch (type) {
+ case MOCK:
+ return (boost::shared_ptr<ClientList>(new SingletonList(client)));
+ case INMEMORY:
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ "\"}}]"), true);
+ return (list);
+ case SQLITE3:
+ // The copy should succeed; if it failed we should notice it in
+ // test cases. However, we check the return value to avoid problems
+ // in some glibcs where "system()" is annotated with the "warn unused
+ // result" attribute.
+ EXPECT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied"));
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied") +
+ "\"}}]"), true);
+ return (list);
+ default:
+ isc_throw(isc::Unexpected,
+ "Unexpected data source type, should be a bug in test code");
+ }
+}
+
+class QueryTest : public ::testing::TestWithParam<DataSrcType> {
protected:
QueryTest() :
- list(memory_client),
qname(Name("www.example.com")), qclass(RRClass::IN()),
qtype(RRType::A()), response(Message::RENDER),
qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -906,22 +834,166 @@ protected:
"glue.delegation.example.com. 3600 IN RRSIG " +
getCommonRRSIGText("AAAA") + "\n" +
"noglue.example.com. 3600 IN RRSIG " +
- getCommonRRSIGText("A"))
+ getCommonRRSIGText("A")),
+ base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
+ nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
+ common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
+ rrsets_added_(false)
{
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3hash_creator_);
+
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
// create and add a matching zone.
mock_finder = new MockZoneFinder();
memory_client.addZone(ZoneFinderPtr(mock_finder));
}
+
+ virtual void SetUp() {
+ // clear the commonly included zone file.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+
+ // We create data source clients here, not in the constructor, so this
+ // doesn't happen for derived test class. This also ensures the
+ // data source clients are configured after setting NSEC3 hash in case
+ // there's dependency.
+ list_ = createDataSrcClientList(GetParam(), memory_client);
+ }
+
+ virtual void TearDown() {
+ // make sure to clear the commonly included zone file to prevent
+ // any remaining contents from affecting the next test.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+ }
+
+ virtual ~QueryTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ void enableNSEC3(const vector<string>& rrsets_to_add) {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ switch (GetParam()) {
+ case MOCK:
+ mock_finder->setNSEC3Flag(true);
+ addRRsets(rrsets_to_add, *list_, "");
+ break;
+ case INMEMORY:
+ addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
+ break;
+ case SQLITE3:
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied"));
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied") +
+ "\"}}]"), true);
+ addRRsets(rrsets_to_add, *new_list, "");
+ list_ = new_list;
+ break;
+ }
+ }
+
+ // A helper to add some RRsets to the test zone in the middle of a test
+ // case. The detailed behavior is quite different depending on the
+ // data source type, and not all parameters are used in all cases.
+ //
+ // Note: due to limitation of its implementation, this method doesn't
+ // work correctly for in-memory if called more than once. This condition
+ // is explicitly checked so any accidental violation would be noted as a
+ // test failure.
+ void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
+ const string& zone_file)
+ {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ ofstream ofs;
+
+ switch (GetParam()) {
+ case MOCK:
+ // directly add them to the mock data source; ignore the passed
+ // list.
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ mock_finder->addRecord(*it);
+ }
+ break;
+ case INMEMORY:
+ ASSERT_FALSE(rrsets_added_);
+ rrsets_added_ = true;
+
+ // dump the RRsets to be added to the placeholder of commonly
+ // included zone file (removing any existing contents) and do
+ // full reconfiguration.
+ ofs.open(common_zone_file.c_str(), ios_base::trunc);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ofs << *it << "\n";
+ ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
+ }
+ ofs.close();
+
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ zone_file + "\"}}]"), true);
+ list_ = new_list;
+ break;
+ case SQLITE3:
+ const Name origin("example.com");
+ ZoneUpdaterPtr updater =
+ list.find(origin, true, false).dsrc_client_->
+ getUpdater(origin, false);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ConstRRsetPtr rrset = textToRRset(*it);
+ updater->addRRset(*rrset);
+ updater->addRRset(*createRRSIG(rrset));
+ }
+ updater->commit();
+ break;
+ }
+ }
+
+private:
+ // A helper for enableNSEC3, creating an RRSIG RRset for the corresponding
+ // non-sig RRset, using the commonly used parameters.
+ static ConstRRsetPtr createRRSIG(ConstRRsetPtr rrset) {
+ RRsetPtr sig_rrset(new RRset(rrset->getName(), rrset->getClass(),
+ RRType::RRSIG(), rrset->getTTL()));
+ sig_rrset->addRdata(generic::RRSIG(
+ getCommonRRSIGText(rrset->getType().
+ toText())));
+ return (sig_rrset);
+ }
+
+protected:
MockZoneFinder* mock_finder;
// We use InMemoryClient here. We could have some kind of mock client
// here, but historically, the Query supported only InMemoryClient
// (originally named MemoryDataSrc) and was tested with it, so we keep
// it like this for now.
InMemoryClient memory_client;
- // A wrapper client list to wrap the single data source.
- SingletonList list;
+
+ boost::shared_ptr<ClientList> list_;
const Name qname;
const RRClass qclass;
const RRType qtype;
@@ -930,6 +1002,37 @@ protected:
const uint16_t query_code;
const string ns_addrs_and_sig_txt; // convenient shortcut
Query query;
+ TestNSEC3Hash nsec3_hash_;
+ vector<string> rrsets_to_add_;
+ const string base_zone_file;
+private:
+ const string nsec3_zone_file;
+ const string common_zone_file;
+ const TestNSEC3HashCreator nsec3hash_creator_;
+ bool rrsets_added_;
+};
+
+// We test the in-memory and SQLite3 data source implementations. SQLite3
+// will require a loadable module, which doesn't work with static link for
+// all platforms.
+INSTANTIATE_TEST_CASE_P(, QueryTest,
+ ::testing::Values(MOCK, INMEMORY
+#ifndef USE_STATIC_LINK
+ , SQLITE3
+#endif
+ ));
+
+// This inherit the QueryTest cases except for the parameterized setup;
+// it's intended to be used selected test cases that only work for mock
+// data sources either because of some limitation or because of the type of
+// tests (relying on a "broken" data source behavior that can't happen with
+// production data source implementations).
+class QueryTestForMockOnly : public QueryTest {
+protected:
+ // Override SetUp() to avoid parameterized setup
+ virtual void SetUp() {
+ list_ = createDataSrcClientList(MOCK, memory_client);
+ }
};
// A wrapper to check resulting response message commonly used in
@@ -969,7 +1072,7 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
}
}
-TEST_F(QueryTest, noZone) {
+TEST_P(QueryTest, noZone) {
// There's no zone in the memory datasource. So the response should have
// REFUSED.
InMemoryClient empty_memory_client;
@@ -979,15 +1082,15 @@ TEST_F(QueryTest, noZone) {
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
-TEST_F(QueryTest, exactMatch) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatch) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchMultipleQueries) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatchMultipleQueries) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -996,27 +1099,26 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
response.clear(isc::dns::Message::RENDER);
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
SCOPED_TRACE("Second query");
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchIgnoreSIG) {
+TEST_P(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
mock_finder->setIncludeRRSIGAnyway(true);
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, dnssecPositive) {
+TEST_P(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
- EXPECT_NO_THROW(query.process(list, qname, qtype, response,
- true));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response, true));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
(www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
@@ -1031,10 +1133,10 @@ TEST_F(QueryTest, dnssecPositive) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, exactAddrMatch) {
+TEST_P(QueryTest, exactAddrMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("noglue.example.com"),
qtype, response));
@@ -1044,10 +1146,10 @@ TEST_F(QueryTest, exactAddrMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexNSMatch) {
+TEST_P(QueryTest, apexNSMatch) {
// find match rrset, omit authority data which has already been provided
// in the answer section from the authority section.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::NS(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1055,10 +1157,16 @@ TEST_F(QueryTest, apexNSMatch) {
}
// test type any query logic
-TEST_F(QueryTest, exactAnyMatch) {
+TEST_P(QueryTest, exactAnyMatch) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1069,10 +1177,10 @@ TEST_F(QueryTest, exactAnyMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexAnyMatch) {
+TEST_P(QueryTest, apexAnyMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
(string(soa_txt) + string(zone_ns_txt) +
@@ -1080,23 +1188,23 @@ TEST_F(QueryTest, apexAnyMatch) {
NULL, ns_addrs_txt, mock_finder->getOrigin());
}
-TEST_F(QueryTest, mxANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
+TEST_P(QueryTest, mxANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("mx.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
(string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
(string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
-TEST_F(QueryTest, glueANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
+TEST_P(QueryTest, glueANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("delegation.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, nodomainANY) {
- EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
+TEST_P(QueryTest, nodomainANY) {
+ EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1105,17 +1213,20 @@ TEST_F(QueryTest, nodomainANY) {
// This tests that when we need to look up Zone's apex NS records for
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
-TEST_F(QueryTest, noApexNS) {
+//
+// This only works with mock data source (for production datasrc the
+// post-load would reject such a zone)
+TEST_F(QueryTestForMockOnly, noApexNS) {
// Disable apex NS record
mock_finder->setApexNSFlag(false);
- EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("noglue.example.com"), qtype,
response), Query::NoApexNS);
// We don't look into the response, as it threw
}
-TEST_F(QueryTest, delegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, delegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
qtype, response));
@@ -1123,19 +1234,19 @@ TEST_F(QueryTest, delegation) {
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, delegationWithDNSSEC) {
+TEST_P(QueryTest, delegationWithDNSSEC) {
// Similar to the previous one, but with requesting DNSSEC.
// In this case the parent zone would behave as unsigned, so the result
// should be just like non DNSSEC delegation.
- query.process(list, Name("www.nosec-delegation.example.com"),
+ query.process(*list_, Name("www.nosec-delegation.example.com"),
qtype, response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
NULL, nosec_delegation_txt, NULL);
}
-TEST_F(QueryTest, secureDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.signed-delegation.example.com"),
qtype, response, true));
@@ -1149,8 +1260,8 @@ TEST_F(QueryTest, secureDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureUnsignedDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true));
@@ -1164,33 +1275,33 @@ TEST_F(QueryTest, secureUnsignedDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3) {
// Similar to the previous case, but the zone is signed with NSEC3,
// and this delegation is NOT an optout.
- const Name insecurechild_name("unsigned-delegation.example.com");
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
// The response should contain the NS and matching NSEC3 with its RRSIG
+ const Name insecurechild_name("unsigned-delegation.example.com");
responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
NULL,
(string(unsigned_delegation_txt) +
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[insecurechild_name] +
+ nsec3_hash_.calculate(insecurechild_name) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
// Similar to the previous case, but the delegation is an optout.
- mock_finder->setNSEC3Flag(true);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
@@ -1202,44 +1313,46 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
NULL,
(string(unsigned_delegation_txt) +
string(nsec3_apex_txt) +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com")] +
+ nsec3_hash_.calculate(Name("www.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, badSecureDelegation) {
+TEST_F(QueryTestForMockOnly, badSecureDelegation) {
+ // This is a broken data source scenario; works only with mock.
+
// Test whether exception is raised if DS query at delegation results in
// something different than SUCCESS or NXRRSET
- EXPECT_THROW(query.process(list,
+ EXPECT_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response, true), Query::BadDS);
// But only if DNSSEC is requested (it shouldn't even try to look for
// the DS otherwise)
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response));
}
-TEST_F(QueryTest, nxdomain) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, nxdomain) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC) {
+TEST_P(QueryTest, nxdomainWithNSEC) {
// NXDOMAIN with DNSSEC proof. We should have SOA, NSEC that proves
// NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
// as well as their RRSIGs.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response, true));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1255,12 +1368,18 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC2) {
+TEST_P(QueryTest, nxdomainWithNSEC2) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about no_txt. In this case the best possible wildcard
// is derived from the next domain of the NSEC that proves NXDOMAIN, and
// the NSEC to provide the non existence of wildcard is different from
// the first NSEC.
- query.process(list, Name("(.no.example.com"), qtype, response,
+ query.process(*list_, Name("(.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1275,10 +1394,17 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
+TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work. This is probably the same type of bug
+ // as nxdomainWithNSEC2 (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about nz_txt. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence of wildcard.
- query.process(list, Name("nx.no.example.com"), qtype, response,
+ query.process(*list_, Name("nx.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
NULL, (string(soa_txt) +
@@ -1290,52 +1416,62 @@ TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC1) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC1) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
mock_finder->setNSECResult(Name("badnsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("badnsec.example.com"),
qtype, response, true),
std::bad_cast);
}
-TEST_F(QueryTest, nxdomainBadNSEC2) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC2) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with an empty NSEC RR.
mock_finder->setNSECResult(Name("emptynsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("emptynsec.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC3) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns SUCCESS. it should be NXDOMAIN.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC4) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC4) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC5) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns non NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
// This is a bit odd, but we'll simply include the returned RRset.
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1350,28 +1486,30 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC6) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC6) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxrrset) {
- EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
+TEST_P(QueryTest, nxrrset) {
+ EXPECT_NO_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC) {
+TEST_P(QueryTest, nxrrsetWithNSEC) {
// NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1521,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, emptyNameWithNSEC) {
+TEST_P(QueryTest, emptyNameWithNSEC) {
// Empty non terminal with DNSSEC proof. This is one of the cases of
// Section 3.1.3.2 of RFC4035.
// mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
@@ -1392,7 +1530,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
// exact match), so we only need one NSEC.
// From the point of the Query::process(), this is actually no different
// from the other NXRRSET case, but we check that explicitly just in case.
- query.process(list, Name("no.example.com"), RRType::A(),
+ query.process(*list_, Name("no.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1404,11 +1542,11 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithoutNSEC) {
+TEST_P(QueryTest, nxrrsetWithoutNSEC) {
// NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
// This is an unexpected event (if the zone is supposed to be properly
// signed with NSECs), but we accept and ignore the oddity.
- query.process(list, Name("nonsec.example.com"), RRType::TXT(),
+ query.process(*list_, Name("nonsec.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1417,10 +1555,10 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC) {
+TEST_P(QueryTest, wildcardNSEC) {
// The qname matches *.wild.example.com. The response should contain
// an NSEC that proves the non existence of a closer name.
- query.process(list, Name("www.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "www") +
@@ -1437,10 +1575,10 @@ TEST_F(QueryTest, wildcardNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC) {
+TEST_P(QueryTest, CNAMEwildNSEC) {
// Similar to the previous case, but the matching wildcard record is
// CNAME.
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
@@ -1453,17 +1591,17 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC3) {
+TEST_P(QueryTest, wildcardNSEC3) {
// Similar to wildcardNSEC, but the zone is signed with NSEC3.
// The next closer is y.wild.example.com, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the apex.
- mock_finder->setNSEC3Flag(true);
-
- // This is NSEC3 for wild.example.com, which will be used in the middle
+ //
+ // Adding NSEC3 for wild.example.com, which will be used in the middle
// of identifying the next closer name.
- mock_finder->addRecord(nsec3_atwild_txt);
+ rrsets_to_add_.push_back(nsec3_atwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("x.y.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("x.y.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "x.y") +
@@ -1474,35 +1612,37 @@ TEST_F(QueryTest, wildcardNSEC3) {
getCommonRRSIGText("NS") + "\n" +
// NSEC3 for the wildcard proof and its RRSIG
string(nsec3_apex_txt) +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC3) {
+TEST_P(QueryTest, CNAMEwildNSEC3) {
// Similar to CNAMEwildNSEC, but with NSEC3.
// The next closer is qname itself, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the www.example.com.
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(nsec3_atcnamewild_txt);
+ rrsets_to_add_.push_back(nsec3_atcnamewild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
string("www.cnamewild.example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("CNAME") + "\n").c_str(),
(string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, badWildcardNSEC3) {
+TEST_F(QueryTestForMockOnly, badWildcardNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNSEC3, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1511,46 +1651,58 @@ TEST_F(QueryTest, badWildcardNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, badWildcardProof1) {
+TEST_F(QueryTestForMockOnly, badWildcardProof1) {
+ // This is a broken data source scenario; works only with mock.
+
// Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
// when NXDOMAIN is expected.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof2) {
+TEST_F(QueryTestForMockOnly, badWildcardProof2) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof3) {
+TEST_F(QueryTestForMockOnly, badWildcardProof3) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// NXRRSET on WILDCARD with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence RRSETs of wildcard.
- query.process(list, Name("www.wild.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1562,12 +1714,18 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// WILDCARD + NXRRSET with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence RRSETs of
// wildcard.
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1582,15 +1740,15 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
// Similar to the previous case, but providing NSEC3 proofs according to
// RFC5155 Section 7.2.5.
- mock_finder->addRecord(nsec3_wild_txt);
- mock_finder->addRecord(nsec3_uwild_txt);
- mock_finder->setNSEC3Flag(true);
+ rrsets_to_add_.push_back(nsec3_wild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,23 +1757,25 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_uwild_txt) +
- mock_finder->hash_map_[Name("uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(nsec3_wild_txt) +
- mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("*.uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Collision) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1624,15 +1784,17 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Broken) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
// wildcard name will be returned. This shouldn't happen in a reasonably
- // NSEC-signed zone, and should result in an exception.
+ // NSEC3-signed zone, and should result in an exception.
mock_finder->setNSEC3Flag(true);
const Name wname("*.uwild.example.com.");
ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
@@ -1641,16 +1803,16 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
mock_finder->addRecord(nsec3_wild_txt);
mock_finder->addRecord(nsec3_uwild_txt);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardEmptyWithNSEC) {
+TEST_P(QueryTest, wildcardEmptyWithNSEC) {
// Empty WILDCARD with DNSSEC proof. We should have SOA, NSEC that proves
// the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence wildcard.
- query.process(list, Name("a.t.example.com"), RRType::A(),
+ query.process(*list_, Name("a.t.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1669,24 +1831,26 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
* This tests that when there's no SOA and we need a negative answer. It should
* throw in that case.
*/
-TEST_F(QueryTest, noSOA) {
+TEST_F(QueryTestForMockOnly, noSOA) {
+ // This is a broken data source scenario; works only with mock.
+
// disable zone's SOA RR.
mock_finder->setSOAFlag(false);
// The NX Domain
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
- EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxrrset.example.com"),
qtype, response), Query::NoSOA);
}
-TEST_F(QueryTest, noMatchZone) {
+TEST_P(QueryTest, noMatchZone) {
// there's a zone in the memory datasource but it doesn't match the qname.
// should result in REFUSED.
- query.process(list, Name("example.org"), qtype, response);
+ query.process(*list_, Name("example.org"), qtype, response);
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
@@ -1696,8 +1860,8 @@ TEST_F(QueryTest, noMatchZone) {
* The MX RRset has two RRs, one pointing to a known domain with
* A record, other to unknown out of zone one.
*/
-TEST_F(QueryTest, MX) {
- query.process(list, Name("mx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MX) {
+ query.process(*list_, Name("mx.example.com"), RRType::MX(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1710,8 +1874,8 @@ TEST_F(QueryTest, MX) {
*
* This should not trigger the additional processing for the exchange.
*/
-TEST_F(QueryTest, MXAlias) {
- query.process(list, Name("cnamemx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MXAlias) {
+ query.process(*list_, Name("cnamemx.example.com"), RRType::MX(),
response);
// there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1730,69 +1894,69 @@ TEST_F(QueryTest, MXAlias) {
* TODO: We currently don't do chaining, so only the CNAME itself should be
* returned.
*/
-TEST_F(QueryTest, CNAME) {
- query.process(list, Name("cname.example.com"), RRType::A(),
+TEST_P(QueryTest, CNAME) {
+ query.process(*list_, Name("cname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME) {
+TEST_P(QueryTest, explicitCNAME) {
// same owner name as the CNAME test but explicitly query for CNAME RR.
// expect the same response as we don't provide a full chain yet.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_RRSET) {
+TEST_P(QueryTest, CNAME_NX_RRSET) {
// Leads to www.example.com, it doesn't have TXT
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
- query.process(list, Name("cname.example.com"), RRType::TXT(),
+ query.process(*list_, Name("cname.example.com"), RRType::TXT(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+TEST_P(QueryTest, explicitCNAME_NX_RRSET) {
// same owner name as the NXRRSET test but explicitly query for CNAME RR.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+TEST_P(QueryTest, CNAME_NX_DOMAIN) {
// Leads to nxdomain.example.com
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional,
// RCODE being NXDOMAIN.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
// RCODE being NOERROR.
- query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
+ query.process(*list_, Name("cnamenxdom.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_nxdom_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+TEST_P(QueryTest, explicitCNAME_NX_DOMAIN) {
// same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
- query.process(list, Name("cnamenxdom.example.com"),
+ query.process(*list_, Name("cnamenxdom.example.com"),
RRType::CNAME(), response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_OUT) {
+TEST_P(QueryTest, CNAME_OUT) {
/*
* This leads out of zone. This should have only the CNAME even
* when we do chaining.
@@ -1801,16 +1965,16 @@ TEST_F(QueryTest, CNAME_OUT) {
* Then the same test should be done with .org included there and
* see what it does (depends on what we want to do)
*/
- query.process(list, Name("cnameout.example.com"), RRType::A(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_out_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_OUT) {
+TEST_P(QueryTest, explicitCNAME_OUT) {
// same owner name as the OUT test but explicitly query for CNAME RR.
- query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1825,8 +1989,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
* as well. This includes tests pointing inside the zone, outside the zone,
* pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
*/
-TEST_F(QueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1841,8 +2005,8 @@ TEST_F(QueryTest, DNAME) {
* ANY is handled specially sometimes. We check it is not the case with
* DNAME.
*/
-TEST_F(QueryTest, DNAME_ANY) {
- query.process(list, Name("www.dname.example.com"), RRType::ANY(),
+TEST_P(QueryTest, DNAME_ANY) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::ANY(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1850,8 +2014,8 @@ TEST_F(QueryTest, DNAME_ANY) {
}
// Test when we ask for DNAME explicitly, it does no synthetizing.
-TEST_F(QueryTest, explicitDNAME) {
- query.process(list, Name("dname.example.com"), RRType::DNAME(),
+TEST_P(QueryTest, explicitDNAME) {
+ query.process(*list_, Name("dname.example.com"), RRType::DNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1862,8 +2026,8 @@ TEST_F(QueryTest, explicitDNAME) {
* Request a RRset at the domain with DNAME. It should not synthetize
* the CNAME, it should return the RRset.
*/
-TEST_F(QueryTest, DNAME_A) {
- query.process(list, Name("dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME_A) {
+ query.process(*list_, Name("dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1874,8 +2038,8 @@ TEST_F(QueryTest, DNAME_A) {
* Request a RRset at the domain with DNAME that is not there (NXRRSET).
* It should not synthetize the CNAME.
*/
-TEST_F(QueryTest, DNAME_NX_RRSET) {
- EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
+TEST_P(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(query.process(*list_, Name("dname.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1887,7 +2051,7 @@ TEST_F(QueryTest, DNAME_NX_RRSET) {
* however, should not throw (and crash the server), but respond with
* YXDOMAIN.
*/
-TEST_F(QueryTest, LongDNAME) {
+TEST_P(QueryTest, LongDNAME) {
// A name that is as long as it can be
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
@@ -1895,7 +2059,7 @@ TEST_F(QueryTest, LongDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1907,14 +2071,14 @@ TEST_F(QueryTest, LongDNAME) {
* This tests that we don't reject valid one by some kind of off by
* one mistake.
*/
-TEST_F(QueryTest, MaxLenDNAME) {
+TEST_P(QueryTest, MaxLenDNAME) {
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
// Check the answer is OK
@@ -1963,7 +2127,10 @@ nsec3Check(bool expected_matched, uint8_t expected_labels,
actual_rrsets.end());
}
-TEST_F(QueryTest, findNSEC3) {
+TEST_F(QueryTestForMockOnly, findNSEC3) {
+ // This test is intended to test the mock data source behavior; no need
+ // to do it for others.
+
// In all test cases in the recursive mode, the closest encloser is the
// apex, and result's closest_labels should be the number of apex labels.
// (In non recursive mode closest_labels should be the # labels of the
@@ -1975,7 +2142,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, non recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), false));
}
@@ -1983,7 +2150,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), true));
}
@@ -1992,7 +2159,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, non recursive");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"),
false));
}
@@ -2002,7 +2169,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
}
@@ -2012,7 +2179,8 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, next closer != qname");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nx.domain.example.com"),
true));
@@ -2022,14 +2190,14 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("largest");
nsec3Check(false, 4,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain2.example.com"),
false));
}
{
SCOPED_TRACE("smallest");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain3.example.com"),
false));
}
@@ -2100,14 +2268,17 @@ private:
const bool have_ds_;
};
-TEST_F(QueryTest, dsAboveDelegation) {
+TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
Name("delegation.example.com"))));
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
RRType::DS(), response, true));
@@ -2121,7 +2292,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, dsAboveDelegationNoData) {
+TEST_P(QueryTest, dsAboveDelegationNoData) {
// Similar to the previous case, but the query is for an unsigned zone
// (which doesn't have a DS at the parent). The response should be a
// "no data" error. The query should still be handled at the parent.
@@ -2131,7 +2302,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("unsigned-delegation.example.com"),
RRType::DS(), response, true));
@@ -2148,8 +2319,8 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// This one checks that type-DS query results in a "no data" response
// when it happens to be sent to the child zone, as described in RFC 4035,
// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
-TEST_F(QueryTest, dsBelowDelegation) {
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2164,9 +2335,10 @@ TEST_F(QueryTest, dsBelowDelegation) {
// Similar to the previous case, but even more pathological: the DS somehow
// exists in the child zone. The Query module should still return SOA.
// In our implementation NSEC/NSEC3 isn't attached in this case.
-TEST_F(QueryTest, dsBelowDelegationWithDS) {
- mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegationWithDS) {
+ rrsets_to_add_.push_back(zone_ds_txt);
+ addRRsets(rrsets_to_add_, *list_, base_zone_file);
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2178,16 +2350,16 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
// DS query received at a completely irrelevant (neither parent nor child)
// server. It should just like the "noZone" test case, but DS query involves
// special processing, so we test it explicitly.
-TEST_F(QueryTest, dsNoZone) {
- query.process(list, Name("example"), RRType::DS(), response,
+TEST_P(QueryTest, dsNoZone) {
+ query.process(*list_, Name("example"), RRType::DS(), response,
true);
responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
}
// DS query for a "grandchild" zone. This should result in normal
// delegation (unless this server also has authority of the grandchild zone).
-TEST_F(QueryTest, dsAtGrandParent) {
- query.process(list, Name("grand.delegation.example.com"),
+TEST_P(QueryTest, dsAtGrandParent) {
+ query.process(*list_, Name("grand.delegation.example.com"),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
(string(delegation_txt) + string(delegation_ds_txt) +
@@ -2201,12 +2373,15 @@ TEST_F(QueryTest, dsAtGrandParent) {
// side and should result in no data with SOA. Note that the server doesn't
// have authority for the "parent". Unlike the dsAboveDelegation test case
// the query should be handled in the child zone, not in the grandparent.
-TEST_F(QueryTest, dsAtGrandParentAndChild) {
+TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
const Name childname("grand.delegation.example.com");
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(childname)));
- query.process(list, childname, RRType::DS(), response, true);
+ query.process(*list_, childname, RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
childname.toText() + " 3600 IN RRSIG " +
@@ -2220,11 +2395,14 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
// DS query for the root name (quite pathological). Since there's no "parent",
// the query will be handled in the root zone anyway, and should (normally)
// result in no data.
-TEST_F(QueryTest, dsAtRoot) {
+TEST_F(QueryTestForMockOnly, dsAtRoot) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
// Pretend to be a root server.
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME())));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2237,11 +2415,14 @@ TEST_F(QueryTest, dsAtRoot) {
// Even more pathological case: A faked root zone actually has its own DS
// query. How we respond wouldn't matter much in practice, but check if
// it behaves as it's intended. This implementation should return the DS.
-TEST_F(QueryTest, dsAtRootWithDS) {
+TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME(),
true)));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2253,19 +2434,19 @@ TEST_F(QueryTest, dsAtRootWithDS) {
}
// Check the signature is present when an NXRRSET is returned
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
// NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_www_txt) + "\n" +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
@@ -2273,7 +2454,9 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
// Check the exception is correctly raised when the NSEC3 thing isn't in the
// zone
-TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
mock_finder->setNSEC3Flag(true);
// We just need it to return false for "matched". This indicates
// there's no exact match for NSEC3 on www.example.com.
@@ -2281,67 +2464,67 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, but does have a matching NSEC3 record
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation.example.com."),
+ query.process(*list_, Name("unsigned-delegation.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, and no directly matching NSEC3 record
// So the response should contain closest encloser proof (and the
// 'next closer' should have opt-out set, though that is not
// actually checked)
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation-optout.example.com."),
+ query.process(*list_, Name("unsigned-delegation-optout.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
// Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
- // Enable NSEC3
- mock_finder->setNSEC3Flag(true);
// This will be the covering NSEC3 for the next closer
- mock_finder->addRecord(nsec3_uwild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
// This will be the covering NSEC3 for the possible wildcard
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ // Enable NSEC3
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
// SOA + its RRSIG
@@ -2350,24 +2533,26 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_uwild_txt) + "\n" +
- mock_finder->hash_map_[Name("uwild.example.com")] +
+ nsec3_hash_.calculate(Name("uwild.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[
- Name("unsigned-delegation.example.com")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadNextNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -2376,12 +2561,14 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadWildcardNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
// NSEC3 for the possible wildcard name, emulating run-time collision.
// This should result in BadNSEC3 exception.
@@ -2395,7 +2582,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3, &wname);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true),
Query::BadNSEC3);
}
@@ -2403,10 +2590,13 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
// The following are tentative tests until we really add tests for the
// query logic for these cases. At that point it's probably better to
// clean them up.
-TEST_F(QueryTest, emptyNameWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
- ZoneFinderContextPtr result = mock_finder->find(
- Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+TEST_P(QueryTest, emptyNameWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
+ const Name qname("no.example.com");
+ ASSERT_TRUE(list_->find(qname).finder_);
+ ZoneFinderContextPtr result =
+ list_->find(qname).finder_->find(qname, RRType::A(),
+ ZoneFinder::FIND_DNSSEC);
EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
EXPECT_FALSE(result->rrset);
EXPECT_TRUE(result->isNSEC3Signed());
@@ -2447,7 +2637,9 @@ loadRRsetVector() {
loadRRsetVectorCallback);
}
-TEST_F(QueryTest, DuplicateNameRemoval) {
+// Note: this is an independent test; don't have to be in the QueryTest
+// fixture.
+TEST(QueryTestSingle, DuplicateNameRemoval) {
// Load some RRsets into the master vector.
loadRRsetVector();
diff --git a/src/bin/auth/tests/testdata/.gitignore b/src/bin/auth/tests/testdata/.gitignore
index b2e0e50..37acc8a 100644
--- a/src/bin/auth/tests/testdata/.gitignore
+++ b/src/bin/auth/tests/testdata/.gitignore
@@ -6,3 +6,13 @@
/shortanswer_fromWire.wire
/simplequery_fromWire.wire
/simpleresponse_fromWire.wire
+/example-base.sqlite3
+/example-base.sqlite3.copied
+/example-base.zone
+/example-base.zone
+/example-common-inc.zone
+/example-nsec3-inc.zone
+/example-nsec3.sqlite3
+/example-nsec3.sqlite3.copied
+/example-nsec3.zone
+/example.zone
diff --git a/src/bin/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am
index a4ea1a5..fed498a 100644
--- a/src/bin/auth/tests/testdata/Makefile.am
+++ b/src/bin/auth/tests/testdata/Makefile.am
@@ -1,4 +1,6 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
+CLEANFILES += example-base.sqlite3 example-nsec3.sqlite3
+CLEANFILES += example-common-inc.zone
BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -24,5 +26,8 @@ EXTRA_DIST += example.com
EXTRA_DIST += example.zone
EXTRA_DIST += example.sqlite3
+EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone
+EXTRA_DIST += example-common-inc-template.zone
+
.spec.wire:
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/bin/auth/tests/testdata/example-base-inc.zone b/src/bin/auth/tests/testdata/example-base-inc.zone
new file mode 100644
index 0000000..bbcbef1
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base-inc.zone
@@ -0,0 +1,236 @@
+;; This file defines a set of RRs commonly used in query tests in the
+;; form of standard master zone file.
+;;
+;; It's a sequence of the following pattern:
+;; ;var=<var_name>
+;; RR_1
+;; RR_2
+;; ..
+;; RR_n
+;;
+;; where var_name is a string that can be used as a variable name in a
+;; C/C++ source file or an empty string. RR_x is a single-line
+;; textual representation of an arbitrary DNS RR.
+;;
+;; If var_name is non empty, the generator script will define a C
+;; variable of C-string type for that set of RRs so that it can be referred
+;; to in the test source file.
+;;
+;; Note that lines beginning ';var=' is no different from other
+;; comment lines as a zone file. It has special meaning only for the
+;; generator script. Obviously, real comment lines cannot begin with
+;; ';var=' (which should be less likely to happen in practice though).
+;;
+;; These RRs will be loaded into in-memory data source in that order.
+;; Note that it may impose stricter restriction on the order of RRs.
+;; In general, each RRset of the same name and type and its RRSIG (if
+;; any) is expected to be grouped.
+
+;var=soa_txt
+example.com. 3600 IN SOA . . 1 0 0 0 0
+;var=zone_ns_txt
+example.com. 3600 IN NS glue.delegation.example.com.
+example.com. 3600 IN NS noglue.example.com.
+example.com. 3600 IN NS example.net.
+
+;var=
+example.com. 3600 IN RRSIG SOA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+example.com. 3600 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Note: the position of the next RR is tricky. It's placed here to
+;; be grouped with the subsequent A RR of the name. But we also want
+;; to group the A RR with other RRs of a different owner name, so the RRSIG
+;; cannot be placed after the A RR. The empty 'var=' specification is
+;; not necessary here, but in case we want to reorganize the ordering
+;; (in which case it's more likely to be needed), we keep it here.
+;var=
+noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=ns_addrs_txt
+noglue.example.com. 3600 IN A 192.0.2.53
+glue.delegation.example.com. 3600 IN A 192.0.2.153
+glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
+
+;var=
+glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=delegation_txt
+delegation.example.com. 3600 IN NS glue.delegation.example.com.
+delegation.example.com. 3600 IN NS noglue.example.com.
+delegation.example.com. 3600 IN NS cname.example.com.
+delegation.example.com. 3600 IN NS example.org.
+
+;var=
+delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Borrowed from the RFC4035
+;var=delegation_ds_txt
+delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
+;var=mx_txt
+mx.example.com. 3600 IN MX 10 www.example.com.
+mx.example.com. 3600 IN MX 20 mailer.example.org.
+mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
+;var=www_a_txt
+www.example.com. 3600 IN A 192.0.2.80
+
+;var=
+www.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cname_txt
+cname.example.com. 3600 IN CNAME www.example.com.
+;var=cname_nxdom_txt
+cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
+;; CNAME Leading out of zone
+;var=cname_out_txt
+cnameout.example.com. 3600 IN CNAME www.example.org.
+;; The DNAME to do tests against
+;var=dname_txt
+dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
+;; Some data at the dname node (allowed by RFC 2672)
+;var=dname_a_txt
+dname.example.com. 3600 IN A 192.0.2.5
+;; This is not inside the zone, this is created at runtime
+;; www.dname.example.com. 3600 IN CNAME www.somethinglong.dnametarget.example.com.
+;; The rest of data won't be referenced from the test cases.
+;var=other_zone_rrs
+cnamemailer.example.com. 3600 IN CNAME www.example.com.
+cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
+mx.delegation.example.com. 3600 IN A 192.0.2.100
+;; Wildcards
+;var=wild_txt
+*.wild.example.com. 3600 IN A 192.0.2.7
+;var=nsec_wild_txt
+*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
+
+;var=
+*.wild.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.wild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cnamewild_txt
+*.cnamewild.example.com. 3600 IN CNAME www.example.org.
+;var=nsec_cnamewild_txt
+*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
+
+;var=
+*.cnamewild.example.com. 3600 IN RRSIG CNAME 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.cnamewild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Wildcard_nxrrset
+;var=wild_txt_nxrrset
+*.uwild.example.com. 3600 IN A 192.0.2.9
+;var=nsec_wild_txt_nxrrset
+*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
+;var=
+*.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=wild_txt_next
+www.uwild.example.com. 3600 IN A 192.0.2.11
+;var=
+www.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_wild_txt_next
+www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
+;; Wildcard empty
+;var=empty_txt
+b.*.t.example.com. 3600 IN A 192.0.2.13
+;var=nsec_empty_txt
+b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
+
+;var=
+b.*.t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=empty_prev_txt
+t.example.com. 3600 IN A 192.0.2.15
+;var=nsec_empty_prev_txt
+t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
+
+;var=
+t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Used in NXDOMAIN proof test. We are going to test some unusual case where
+;; the best possible wildcard is below the "next domain" of the NSEC RR that
+;; proves the NXDOMAIN, i.e.,
+;; mx.example.com. (exist)
+;; (.no.example.com. (qname, NXDOMAIN)
+;; ).no.example.com. (exist)
+;; *.no.example.com. (best possible wildcard, not exist)
+;var=no_txt
+\).no.example.com. 3600 IN AAAA 2001:db8::53
+;; NSEC records.
+;var=nsec_apex_txt
+example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
+;var=
+example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_mx_txt
+mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+
+;var=
+mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=nsec_no_txt
+\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+
+;var=
+\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
+;; non existence of wildcard. The following records will be used for that
+;; test.
+;; ).no.example.com. (exist, whose NSEC proves everything)
+;; *.no.example.com. (best possible wildcard, not exist)
+;; nx.no.example.com. (NXDOMAIN)
+;; nz.no.example.com. (exist)
+;var=nz_txt
+nz.no.example.com. 3600 IN AAAA 2001:db8::5300
+;var=nsec_nz_txt
+nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
+;var=nsec_nxdomain_txt
+noglue.example.com. 3600 IN NSEC nonsec.example.com. A
+
+;var=
+noglue.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; NSEC for the normal NXRRSET case
+;var=nsec_www_txt
+www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
+
+;var=
+www.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Authoritative data without NSEC
+;var=nonsec_a_txt
+nonsec.example.com. 3600 IN A 192.0.2.0
+
+;; (Secure) delegation data; Delegation with DS record
+;var=signed_delegation_txt
+signed-delegation.example.com. 3600 IN NS ns.example.net.
+;var=signed_delegation_ds_txt
+signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
+
+;var=
+signed-delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; (Secure) delegation data; Delegation without DS record (and both NSEC
+;; and NSEC3 denying its existence)
+;var=unsigned_delegation_txt
+unsigned-delegation.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_nsec_txt
+unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
+
+;var=
+unsigned-delegation.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Delegation without DS record, and no direct matching NSEC3 record
+;var=unsigned_delegation_optout_txt
+unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_optout_nsec_txt
+unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
+
+;; (Secure) delegation data; Delegation where the DS lookup will raise an
+;; exception.
+;var=bad_delegation_txt
+bad-delegation.example.com. 3600 IN NS ns.example.net.
+
+;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
+;; or NSEC3 that proves it.
+;var=nosec_delegation_txt
+nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example-base.zone.in b/src/bin/auth/tests/testdata/example-base.zone.in
new file mode 100644
index 0000000..63d2af0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base.zone.in
@@ -0,0 +1,7 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests.
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example-common-inc-template.zone b/src/bin/auth/tests/testdata/example-common-inc-template.zone
new file mode 100644
index 0000000..d7259bf
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-common-inc-template.zone
@@ -0,0 +1,5 @@
+;;
+;; This is an initial template of part of test zone file used in query test
+;; and expected to be included from other zone files. This is
+;; intentionally kept empty.
+;;
diff --git a/src/bin/auth/tests/testdata/example-nsec3-inc.zone b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
new file mode 100644
index 0000000..7742df0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
@@ -0,0 +1,16 @@
+;; See query_testzone_data.txt for general notes.
+
+;; NSEC3PARAM. This is needed for database-based data source to
+;; signal the zone is NSEC3-signed
+;var=
+example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
+
+;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
+;var=nsec3_apex_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
+;var=nsec3_apex_rrsig_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec3_www_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+;var=nsec3_www_rrsig_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
diff --git a/src/bin/auth/tests/testdata/example-nsec3.zone.in b/src/bin/auth/tests/testdata/example-nsec3.zone.in
new file mode 100644
index 0000000..aaf3d6a
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3.zone.in
@@ -0,0 +1,8 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests including NSEC3 records, making the zone is "NSEC3 signed".
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_srcdir@/example-nsec3-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone
deleted file mode 100644
index af0b618..0000000
--- a/src/bin/auth/tests/testdata/example.zone
+++ /dev/null
@@ -1,121 +0,0 @@
-;;
-;; This is a complete (but crafted and somewhat broken) zone file used
-;; in query tests.
-;;
-
-example.com. 3600 IN SOA . . 0 0 0 0 0
-example.com. 3600 IN NS glue.delegation.example.com.
-example.com. 3600 IN NS noglue.example.com.
-example.com. 3600 IN NS example.net.
-example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-glue.delegation.example.com. 3600 IN A 192.0.2.153
-glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
-noglue.example.com. 3600 IN A 192.0.2.53
-delegation.example.com. 3600 IN NS glue.delegation.example.com.
-delegation.example.com. 3600 IN NS noglue.example.com.
-delegation.example.com. 3600 IN NS cname.example.com.
-delegation.example.com. 3600 IN NS example.org.
-;; Borrowed from the RFC4035
-delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-mx.example.com. 3600 IN MX 10 www.example.com.
-mx.example.com. 3600 IN MX 20 mailer.example.org.
-mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
-www.example.com. 3600 IN A 192.0.2.80
-cname.example.com. 3600 IN CNAME www.example.com.
-cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
-;; CNAME Leading out of zone
-cnameout.example.com. 3600 IN CNAME www.example.org.
-;; The DNAME to do tests against
-dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
-;; Some data at the dname node (allowed by RFC 2672)
-dname.example.com. 3600 IN A 192.0.2.5
-;; The rest of data won't be referenced from the test cases.
-cnamemailer.example.com. 3600 IN CNAME www.example.com.
-cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
-mx.delegation.example.com. 3600 IN A 192.0.2.100
-;; Wildcards
-*.wild.example.com. 3600 IN A 192.0.2.7
-*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
-*.cnamewild.example.com. 3600 IN CNAME www.example.org.
-*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
-;; Wildcard_nxrrset
-*.uwild.example.com. 3600 IN A 192.0.2.9
-*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
-www.uwild.example.com. 3600 IN A 192.0.2.11
-www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
-;; Wildcard empty
-b.*.t.example.com. 3600 IN A 192.0.2.13
-b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
-t.example.com. 3600 IN A 192.0.2.15
-t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
-;; Used in NXDOMAIN proof test. We are going to test some unusual case where
-;; the best possible wildcard is below the "next domain" of the NSEC RR that
-;; proves the NXDOMAIN, i.e.,
-;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
-;; *.no.example.com. (best possible wildcard, not exist)
-\).no.example.com. 3600 IN AAAA 2001:db8::53
-;; NSEC records.
-example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
-mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
-\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
-;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
-;; non existence of wildcard. The following records will be used for that
-;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
-;; *.no.example.com. (best possible wildcard, not exist)
-;; nx.no.example.com. (NXDOMAIN)
-;; nz.no.example.com. (exist)
-nz.no.example.com. 3600 IN AAAA 2001:db8::5300
-nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
-noglue.example.com. 3600 IN NSEC nonsec.example.com. A
-
-;; NSEC for the normal NXRRSET case
-www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
-
-;; Authoritative data without NSEC
-nonsec.example.com. 3600 IN A 192.0.2.0
-
-;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-
-;; NSEC3 for wild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse
-;; other tests)
-b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-;; NSEC3 for uwild.example.com. (will be added on demand)
-t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-
-;; (Secure) delegation data; Delegation with DS record
-signed-delegation.example.com. 3600 IN NS ns.example.net.
-signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
-
-;; (Secure) delegation data; Delegation without DS record (and both NSEC
-;; and NSEC3 denying its existence)
-unsigned-delegation.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
-;; This one will be added on demand
-q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG
-
-;; Delegation without DS record, and no direct matching NSEC3 record
-unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
-
-;; (Secure) delegation data; Delegation where the DS lookup will raise an
-;; exception.
-bad-delegation.example.com. 3600 IN NS ns.example.net.
-
-;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
-;; or NSEC3 that proves it.
-nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example.zone.in b/src/bin/auth/tests/testdata/example.zone.in
new file mode 100644
index 0000000..608c09f
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example.zone.in
@@ -0,0 +1,6 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests, excluding NSEC3 records.
+;;
+
+$INCLUDE @abs_builddir@/example-base.zone
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 2ce86ab..e32544a 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -160,7 +160,7 @@
<citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
daemon to use.
The default is
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+ <filename>/usr/local/var/bind10/msg_socket</filename>.
<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
</para>
</listitem>
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index aba8da5..9414ed6 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -154,7 +154,7 @@ The boss module received the given signal.
% BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
The boss module tried to restart a component after it failed (crashed)
unexpectedly, but the boss then found that the component had been removed
-from its local configuration of components to run. This is an unusal
+from its local configuration of components to run. This is an unusual
situation but can happen if the administrator removes the component from
the configuration after the component's crash and before the restart time.
The boss module simply skipped restarting that module, and the whole system
@@ -262,7 +262,7 @@ indicated OS API function with given error.
The boss forwards a request for a socket to the socket creator.
% BIND10_STARTED_CC started configuration/command session
-Debug message given when BIND 10 has successfull started the object that
+Debug message given when BIND 10 has successfully started the object that
handles configuration and commands.
% BIND10_STARTED_PROCESS started %1
@@ -308,13 +308,6 @@ During the startup process, a number of messages are exchanged between the
Boss process and the processes it starts. This error is output when a
message received by the Boss process is not recognised.
-% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
-The resolver is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
% BIND10_STOP_PROCESS asking %1 to shut down
The boss module is sending a shutdown command to the given module over
the message channel.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 882653b..6fe5485 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -103,8 +103,31 @@ VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for boot_time of Boss
_BASETIME = time.gmtime()
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file. We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
class ProcessInfoError(Exception): pass
+class ChangeUserError(Exception):
+ '''Exception raised when setuid/setgid fails.
+
+ When raised, it's expected to be propagated via underlying component
+ management modules to the top level so that it will help provide useful
+ fatal error message.
+
+ '''
+ pass
+
class ProcessInfo:
"""Information about a process"""
@@ -206,8 +229,8 @@ class BoB:
# restart. Components manage their own restart schedule now
self.components_to_restart = []
self.runnable = False
- self.uid = setuid
- self.gid = setgid
+ self.__uid = setuid
+ self.__gid = setgid
self.username = username
self.verbose = verbose
self.nokill = nokill
@@ -269,6 +292,31 @@ class BoB:
# Update the configuration
self._component_configurator.reconfigure(comps)
+ def change_user(self):
+ '''Change the user and group to those specified on construction.
+
+ This method is expected to be called by a component on initial
+ startup when the system is ready to switch the user and group
+ (i.e., once all components that need the privilege of the original
+ user have started).
+ '''
+ try:
+ if self.__gid is not None:
+ logger.info(BIND10_SETGID, self.__gid)
+ posix.setgid(self.__gid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change group: ' + str(ex))
+
+ try:
+ if self.__uid is not None:
+ posix.setuid(self.__uid)
+ # We use one-shot logger after setuid here. This will
+ # detect any permission issue regarding logging due to the
+ # result of setuid at the earliest opportunity.
+ isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change user: ' + str(ex))
+
def config_handler(self, new_config):
# If this is initial update, don't do anything now, leave it to startup
if not self.runnable:
@@ -578,8 +626,6 @@ class BoB:
are pure speculation. As with the auth daemon, they should be
read from the configuration database.
"""
- if self.uid is not None and self.__started:
- logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
self.curproc = "b10-resolver"
# XXX: this must be read from the configuration manager in the future
resargs = ['b10-resolver']
@@ -646,6 +692,9 @@ class BoB:
try:
self.c_channel_env = c_channel_env
self.start_all_components()
+ except ChangeUserError as e:
+ self.kill_started_components()
+ return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
except Exception as e:
self.kill_started_components()
return "Unable to start " + self.curproc + ": " + str(e)
@@ -1155,7 +1204,19 @@ def remove_lock_files():
for f in lockfiles:
fname = lpath + '/' + f
if os.path.isfile(fname):
- os.unlink(fname)
+ try:
+ os.unlink(fname)
+ except OSError as e:
+ # We catch and ignore permission related error on unlink.
+ # This can happen if bind10 started with -u, created a lock
+ # file as a privileged user, but the directory is not writable
+ # for the changed user. This setup will cause immediate
+ # start failure, and we leave verbose error message including
+ # the leftover lock file, so it should be acceptable to ignore
+ # it (note that it doesn't make sense to log this event at
+ # this poitn)
+ if e.errno != errno.EPERM and e.errno != errno.EACCES:
+ raise
return
@@ -1173,13 +1234,7 @@ def main():
except RuntimeError as e:
sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
str(e))
- sys.stderr.write("""\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user. Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user.
-""")
+ sys.stderr.write(NOTE_ON_LOCK_FILE)
sys.exit(1)
# Check user ID.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 409790c..ccfa831 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -341,6 +341,18 @@ class TestCacheCommands(unittest.TestCase):
class TestBoB(unittest.TestCase):
+ def setUp(self):
+ # Save original values that may be tweaked in some tests
+ self.__orig_setgid = bind10_src.posix.setgid
+ self.__orig_setuid = bind10_src.posix.setuid
+ self.__orig_logger_class = isc.log.Logger
+
+ def tearDown(self):
+ # Restore original values saved in setUp()
+ bind10_src.posix.setgid = self.__orig_setgid
+ bind10_src.posix.setuid = self.__orig_setuid
+ isc.log.Logger = self.__orig_logger_class
+
def test_init(self):
bob = BoB()
self.assertEqual(bob.verbose, False)
@@ -349,10 +361,56 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
self.assertIsNone(bob._socket_cache)
+ def __setgid(self, gid):
+ self.__gid_set = gid
+
+ def __setuid(self, uid):
+ self.__uid_set = uid
+
+ def test_change_user(self):
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+
+ self.__gid_set = None
+ self.__uid_set = None
+ bob = BoB()
+ bob.change_user()
+ # No gid/uid set in boss, nothing called.
+ self.assertIsNone(self.__gid_set)
+ self.assertIsNone(self.__uid_set)
+
+ BoB(setuid=42, setgid=4200).change_user()
+ # This time, it get's called
+ self.assertEqual(4200, self.__gid_set)
+ self.assertEqual(42, self.__uid_set)
+
+ def raising_set_xid(gid_or_uid):
+ ex = OSError()
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ raise ex
+
+ # Let setgid raise an exception
+ bind10_src.posix.setgid = raising_set_xid
+ bind10_src.posix.setuid = self.__setuid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let initial log output after setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+ isc.log.Logger = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
def test_set_creator(self):
"""
Test the call to set_creator. First time, the cache is created
@@ -423,7 +481,6 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
def test_command_handler(self):
@@ -2021,8 +2078,10 @@ class TestBossComponents(unittest.TestCase):
def start_all_components(self):
self.started = True
- if self.throw:
+ if self.throw is True:
raise Exception('Assume starting components has failed.')
+ elif self.throw:
+ raise self.throw
def kill_started_components(self):
self.killed = True
@@ -2067,6 +2126,12 @@ class TestBossComponents(unittest.TestCase):
r = bob.startup()
self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+ # Check failure of changing user results in a different message
+ bob = MockBobStartup(bind10_src.ChangeUserError('failed to chusr'))
+ r = bob.startup()
+ self.assertIn('failed to chusr', r)
+ self.assertTrue(bob.killed)
+
# Check the case when socket file already exists
isc.cc.Session = DummySessionSocketExists
bob = MockBobStartup(False)
@@ -2277,11 +2342,15 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.exists(self.lockfile_testpath))
os.mkdir(self.lockfile_testpath)
self.assertTrue(os.path.isdir(self.lockfile_testpath))
+ self.__isfile_orig = bind10_src.os.path.isfile
+ self.__unlink_orig = bind10_src.os.unlink
def tearDown(self):
os.rmdir(self.lockfile_testpath)
self.assertFalse(os.path.isdir(self.lockfile_testpath))
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+ bind10_src.os.path.isfile = self.__isfile_orig
+ bind10_src.os.unlink = self.__unlink_orig
def test_remove_lock_files(self):
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
@@ -2305,6 +2374,28 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
bind10_src.remove_lock_files()
+ def test_remove_lock_files_fail(self):
+ # Permission error on unlink is ignored; other exceptions are really
+ # unexpected and propagated.
+ def __raising_unlink(unused, ex):
+ raise ex
+
+ bind10_src.os.path.isfile = lambda _: True
+ os_error = OSError()
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+ os_error.errno = errno.EPERM
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.EACCES
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.ENOENT
+ self.assertRaises(OSError, bind10_src.remove_lock_files)
+
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+ self.assertRaises(Exception, bind10_src.remove_lock_files)
+
def test_get_signame(self):
# just test with some samples
signame = bind10_src.get_signame(signal.SIGTERM)
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 37b74e7..7c2b2af 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -208,12 +208,13 @@ WARNING: Python readline module isn't available, so the command line editor
return True
def login_to_cmdctl(self):
- '''Login to cmdctl with the username and password inputted
- from user. After the login is sucessful, the username and
+ '''Login to cmdctl with the username and password given by
+ the user. After the login is sucessful, the username and
password will be saved in 'default_user.csv', when run the next
time, username and password saved in 'default_user.csv' will be
used first.
'''
+ # Look at existing username/password combinations and try to log in
users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
@@ -230,15 +231,18 @@ WARNING: Python readline module isn't available, so the command line editor
print(data + ' login as ' + row[0])
return True
+ # No valid logins were found, prompt the user for a username/password
count = 0
- print("[TEMP MESSAGE]: username :root password :bind10")
+ print('No stored password file found, please see sections '
+ '"Configuration specification for b10-cmdctl" and "bindctl '
+ 'command-line options" of the BIND 10 guide.')
while True:
count = count + 1
if count > 3:
print("Too many authentication failures")
return False
- username = input("Username:")
+ username = input("Username: ")
passwd = getpass.getpass()
param = {'username': username, 'password' : passwd}
try:
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index ff5706c..d8688f9 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -136,7 +136,7 @@
<refsect1>
<title>FILES</title>
<!-- TODO: fix path -->
- <para><filename>/usr/local/var/bind10-devel/b10-config.db</filename>
+ <para><filename>/usr/local/var/bind10/b10-config.db</filename>
— Configuration storage file.
</para>
</refsect1>
diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml
index 1e3c8e3..5ac8783 100644
--- a/src/bin/cmdctl/b10-certgen.xml
+++ b/src/bin/cmdctl/b10-certgen.xml
@@ -190,7 +190,7 @@
To update an expired certificate in BIND 10 that has been installed to
/usr/local:
<screen>
-$> cd /usr/local/etc/bind10-devel/
+$> cd /usr/local/etc/bind10/
$> b10-certgen
cmdctl-certfile.pem failed to verify: certificate has expired
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index e01d5a2..4b1b32f 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.xml
@@ -147,21 +147,21 @@
<varname>accounts_file</varname> defines the path to the
user accounts database.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
</para>
<para>
<varname>cert_file</varname> defines the path to the
PEM certificate file.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
</para>
<para>
<varname>key_file</varname> defines the path to the PEM private key
file.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
</para>
<!-- TODO: formating -->
@@ -187,17 +187,17 @@
<!-- TODO: permissions -->
<!-- TODO: what about multiple accounts? -->
<!-- TODO: shouldn't the password file name say cmdctl in it? -->
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>
— account database containing the name, hashed password,
and the salt.
</para>
<!-- TODO: replace /usr/local -->
<!-- TODO: permissions -->
<!-- TODO: shouldn't have both in same file, will be configurable -->
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>
— contains the Private key.
</para>
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>
— contains the Certificate.
</para>
</refsect1>
diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes
index 90ede92..2fbd000 100644
--- a/src/bin/dbutil/dbutil_messages.mes
+++ b/src/bin/dbutil/dbutil_messages.mes
@@ -53,7 +53,7 @@ inconsistent state, and it is advised to restore it from the backup that was
created when b10-dbutil started.
% DBUTIL_EXECUTE Executing SQL statement: %1
-Debug message; the given SQL statement is executed
+Debug message; the given SQL statement is executed.
% DBUTIL_FILE Database file: %1
The database file that is being checked.
@@ -67,7 +67,7 @@ The given database statement failed to execute. The error is shown in the
message.
% DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
-There were too many command-line arguments to b10-dbutil
+There were too many command-line arguments to b10-dbutil.
% DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
The user aborted the upgrade, and b10-dbutil will now exit.
@@ -95,7 +95,7 @@ again.
% DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1
An unexpected error occurred while b10-dbutil was preparing to upgrade the
-database schema. The error is shown in the message
+database schema. The error is shown in the message.
% DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed
The database schema update was completed successfully.
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index c896591..28f08f7 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -58,6 +58,7 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
@@ -65,6 +66,5 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-
b10_dhcp4dir = $(pkgdatadir)
b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 08d16f8..fdb4240 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.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
@@ -13,9 +13,15 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config/ccsession.h>
-#include <dhcpsrv/cfgmgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/dhcp4_log.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
@@ -25,45 +31,81 @@
#include <map>
using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
/// @brief auxiliary type used for storing element name and its parser
typedef pair<string, ConstElementPtr> ConfigPair;
/// @brief a factory method that will create a parser for a given element name
-typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
/// @brief a collection of factories that creates parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
/// @brief a collection of pools
///
/// That type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
typedef std::vector<Pool4Ptr> PoolStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
+
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
/// @brief global string parameters that will be used as defaults.
StringStorage string_defaults;
+/// @brief Global storage for options that will be used as defaults.
+OptionStorage option_defaults;
+
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
/// @brief a dummy configuration parser
///
/// It is a debugging parser. It does not configure anything,
/// will accept any configuration and will just print it out
/// on commit. Useful for debugging existing configurations and
/// adding new ones.
-class DebugParser : public Dhcp4ConfigParser {
+class DebugParser : public DhcpConfigParser {
public:
/// @brief Constructor
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -72,7 +114,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -86,7 +128,7 @@ public:
/// This is a method required by base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -98,7 +140,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* Factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -110,6 +152,85 @@ private:
ConstElementPtr value_;
};
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -117,27 +238,36 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class Uint32Parser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
/// @throw BadValue if supplied value could not be base to uint32_t
+ /// or the parameter name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
int64_t check;
string x = value->str();
try {
@@ -157,31 +287,27 @@ public:
// value is small enough to fit
value_ = static_cast<uint32_t>(check);
-
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet4ConfigParser when constructing
- /// the subnet.
+ /// @brief Stores the parsed uint32_t value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs Uint32Parser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv4ConfigInherit for details.
+ /// See @ref dhcpv4ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(Uint32Storage* storage) {
@@ -206,47 +332,48 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref Dhcp4ConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class StringParser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
:storage_(&string_defaults), param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
/// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// @ref setStorage() for details.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
value_ = value->str();
boost::erase_all(value_, "\"");
-
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parser. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
+ /// @brief Stores the parsed value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
@@ -279,7 +406,7 @@ private:
/// designates all interfaces.
///
/// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public Dhcp4ConfigParser {
+class InterfaceListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -317,7 +444,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -336,7 +463,7 @@ private:
/// before build(). Otherwise exception will be thrown.
///
/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public Dhcp4ConfigParser {
+class PoolParser : public DhcpConfigParser {
public:
/// @brief constructor.
@@ -352,7 +479,7 @@ public:
/// interface name.
/// @param pools_list list of pools defined for a subnet
/// @throw InvalidOperation if storage was not specified (setStorage() not called)
- /// @throw Dhcp4ConfigError when pool parsing fails
+ /// @throw DhcpConfigError when pool parsing fails
void build(ConstElementPtr pools_list) {
// setStorage() should have been called before build
if (!pools_) {
@@ -392,12 +519,12 @@ public:
// be checked in Pool4 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp4ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "Failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool4Ptr pool(new Pool4(addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -410,11 +537,11 @@ public:
Pool4Ptr pool(new Pool4(min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -429,17 +556,22 @@ public:
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -449,13 +581,653 @@ private:
/// That is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
+};
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Class constructor.
+ OptionDataParser(const std::string&)
+ : options_(NULL),
+ // initialize option to NULL ptr
+ option_descriptor_(false) { }
+
+ /// @brief Parses the single option data.
+ ///
+ /// This method parses the data of a single option from the configuration.
+ /// The option data includes option name, option code and data being
+ /// carried by this option. Eventually it creates the instance of the
+ /// option.
+ ///
+ /// @warning setStorage must be called with valid storage pointer prior
+ /// to calling this method.
+ ///
+ /// @param option_data_entries collection of entries that define value
+ /// for a particular option.
+ /// @throw DhcpConfigError if invalid parameter specified in
+ /// the configuration.
+ /// @throw isc::InvalidOperation if failed to set storage prior to
+ /// calling build.
+ virtual void build(ConstElementPtr option_data_entries) {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ "parsing option data.");
+ }
+ BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+ ParserPtr parser;
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
+ boost::shared_ptr<StringParser>
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
+ if (name_parser) {
+ name_parser->setStorage(&string_values_);
+ parser = name_parser;
+ }
+ } else if (param.first == "code") {
+ boost::shared_ptr<Uint32Parser>
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+ if (value_parser) {
+ value_parser->setStorage(&boolean_values_);
+ parser = value_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError,
+ "Parser error: option-data parameter not supported: "
+ << param.first);
+ }
+ parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
+ }
+ // Try to create the option instance.
+ createOption();
+ }
+
+ /// @brief Commits option value.
+ ///
+ /// This function adds a new option to the storage or replaces an existing option
+ /// with the same code.
+ ///
+ /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
+ /// to call build() prior to commit. If that happens data in the storage
+ /// remain un-modified.
+ virtual void commit() {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
+ "commiting option data.");
+ } else if (!option_descriptor_.option) {
+ // Before we can commit the new option should be configured. If it is not
+ // than somebody must have called commit() before build().
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+ uint16_t opt_type = option_descriptor_.option->getType();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ // Try to find options with the particular option code in the main
+ // storage. If found, remove these options because they will be
+ // replaced with new one.
+ Subnet::OptionContainerTypeRange range =
+ idx.equal_range(opt_type);
+ if (std::distance(range.first, range.second) > 0) {
+ idx.erase(range.first, range.second);
+ }
+ // Append new option to the main storage.
+ options_->addItem(option_descriptor_, option_space_);
+ }
+
+ /// @brief Set storage for the parser.
+ ///
+ /// Sets storage for the parser. This storage points to the
+ /// vector of options and is used by multiple instances of
+ /// OptionDataParser. Each instance creates exactly one object
+ /// of dhcp::Option or derived type and appends it to this
+ /// storage.
+ ///
+ /// @param storage pointer to the options storage
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+private:
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @warning this function does not check if options_ storage pointer
+ /// is intitialized but this check is not needed here because it is done
+ /// in the \ref build function.
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ void createOption() {
+ // Option code is held in the uint32_t storage but is supposed to
+ // be uint16_t value. We need to check that value in the configuration
+ // does not exceed range of uint16_t and is not zero.
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
+ if (option_code == 0) {
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
+ << " be equal to zero. Option code '0' is reserved in"
+ << " DHCPv4.");
+ } else if (option_code > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
+ << " exceed " << std::numeric_limits<uint16_t>::max());
+ }
+ // Check that the option name has been specified, is non-empty and does not
+ // contain spaces.
+ // @todo possibly some more restrictions apply here?
+ std::string option_name = getParam<std::string>("name", string_values_);
+ if (option_name.empty()) {
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
+ << " empty");
+ } else if (option_name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
+ << " spaces");
+ }
+
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V4, option_code);
+
+ } else if (option_space == "dhcp6") {
+ isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
+ << " for DHCPv6 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
+ // Get option data from the configuration database ('data' field).
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
+ }
+
+ OptionPtr option;
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
+ // @todo We have a limited set of option definitions intiialized at the moment.
+ // In the future we want to initialize option definitions for all options.
+ // Consequently an error will be issued if an option definition does not exist
+ // for a particular option code. For now it is ok to create generic option
+ // if definition does not exist.
+ OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
+ binary));
+ // The created option is stored in option_descriptor_ class member until the
+ // commit stage when it is inserted into the main storage. If an option with the
+ // same code exists in main storage already the old option is replaced.
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } else {
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V4, option_code, data_tokens) :
+ def->optionFactory(Option::V4, option_code, binary);
+ Subnet::OptionDescriptor desc(option, false);
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
+ << ex.what());
+ }
+ }
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
+ }
+
+ /// Storage for uint32 values (e.g. option code).
+ Uint32Storage uint32_values_;
+ /// Storage for string values (e.g. option name or data).
+ StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Pointer to options storage. This storage is provided by
+ /// the calling class and is shared by all OptionDataParser objects.
+ OptionStorage* options_;
+ /// Option descriptor holds newly configured option.
+ Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Unless otherwise specified, parsed options will be stored in
+ /// a global option container (option_default). That storage location
+ /// is overriden on a subnet basis.
+ OptionDataListParser(const std::string&)
+ : options_(&option_defaults), local_options_() { }
+
+ /// @brief Parses entries that define options' data for a subnet.
+ ///
+ /// This method iterates over all entries that define option data
+ /// for options within a single subnet and creates options' instances.
+ ///
+ /// @param option_data_list pointer to a list of options' data sets.
+ /// @throw DhcpConfigError if option parsing failed.
+ void build(ConstElementPtr option_data_list) {
+ BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+ boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
+ // options_ member will hold instances of all options thus
+ // each OptionDataParser takes it as a storage.
+ parser->setStorage(&local_options_);
+ // Build the instance of a single option.
+ parser->build(option_value);
+ // Store a parser as it will be used to commit.
+ parsers_.push_back(parser);
+ }
+ }
+
+ /// @brief Set storage for option instances.
+ ///
+ /// @param storage pointer to options storage.
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+
+ /// @brief Commit all option values.
+ ///
+ /// This function invokes commit for all option values.
+ void commit() {
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+ // Parsing was successful and we have all configured
+ // options in local storage. We can now replace old values
+ // with new values.
+ std::swap(local_options_, *options_);
+ }
+
+ /// @brief Create DhcpDataListParser object
+ ///
+ /// @param param_name param name.
+ ///
+ /// @return DhcpConfigParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDataListParser(param_name));
+ }
+
+ /// Intermediate option storage. This storage is used by
+ /// lower level parsers to add new options. Values held
+ /// in this storage are assigned to main storage (options_)
+ /// if overall parsing was successful.
+ OptionStorage local_options_;
+ /// Pointer to options instances storage.
+ OptionStorage* options_;
+ /// Collection of parsers;
+ ParserCollection parsers_;
+};
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
};
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
/// for received configuration parameters as needed.
-class Subnet4ConfigParser : public Dhcp4ConfigParser {
+class Subnet4ConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -470,36 +1242,45 @@ public:
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
-
ParserPtr parser(createSubnet4ConfigParser(param.first));
-
- // if this is an Uint32 parser, tell it to store the values
- // in values_, rather than in global storage
- boost::shared_ptr<Uint32Parser> uint_parser =
- boost::dynamic_pointer_cast<Uint32Parser>(parser);
- if (uint_parser) {
- uint_parser->setStorage(&uint32_values_);
- } else {
-
- boost::shared_ptr<StringParser> string_parser =
- boost::dynamic_pointer_cast<StringParser>(parser);
- if (string_parser) {
- string_parser->setStorage(&string_values_);
- } else {
-
- boost::shared_ptr<PoolParser> pool_parser =
- boost::dynamic_pointer_cast<PoolParser>(parser);
- if (pool_parser) {
- pool_parser->setStorage(&pools_);
- }
- }
+ // The actual type of the parser is unknown here. We have to discover
+ // the parser type here to invoke the corresponding setStorage function
+ // on it. We discover parser type by trying to cast the parser to various
+ // parser types and checking which one was successful. For this one
+ // a setStorage and build methods are invoked.
+
+ // Try uint32 type parser.
+ if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+ param.second) &&
+ // Try string type parser.
+ !buildParser<StringParser, StringStorage >(parser, string_values_,
+ param.second) &&
+ // Try pool parser.
+ !buildParser<PoolParser, PoolStorage >(parser, pools_,
+ param.second) &&
+ // Try option data parser.
+ !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+ param.second)) {
+ // Appropriate parsers are created in the createSubnet6ConfigParser
+ // and they should be limited to those that we check here for. Thus,
+ // if we fail to find a matching parser here it is a programming error.
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
-
- parser->build(param.second);
- parsers_.push_back(parser);
+ }
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
}
- // Ok, we now have subnet parsed
+ // Create a subnet.
+ createSubnet();
}
/// @brief commits received configuration.
@@ -508,26 +1289,84 @@ public:
/// storing the values that are actually consumed here. Pool definitions
/// created in other parsers are used here and added to newly created Subnet4
/// objects. Subnet4 are then added to DHCP CfgMgr.
- /// @throw Dhcp4ConfigError if there are any issues encountered during commit
+ /// @throw DhcpConfigError if there are any issues encountered during commit
void commit() {
+ if (subnet_) {
+ CfgMgr::instance().addSubnet4(subnet_);
+ }
+ }
+
+private:
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
@@ -539,16 +1378,67 @@ public:
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+ subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool4(*it);
+ subnet_->addPool(*it);
}
- CfgMgr::instance().addSubnet4(subnet);
- }
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
-private:
+ // Check all global options and add them to the subnet object if
+ // they have been configured in the global scope. If they have been
+ // configured in the subnet scope we don't add global option because
+ // the one configured in the subnet scope always takes precedence.
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+ }
+ }
/// @brief creates parsers for entries in subnet definition
///
@@ -557,14 +1447,15 @@ private:
/// @param config_id name od the entry
/// @return parser object for specified entry name
/// @throw NotImplemented if trying to create a parser for unknown config element
- Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+ DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["subnet"] = StringParser::Factory;
- factories["pool"] = PoolParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -572,13 +1463,13 @@ private:
// return new DebugParser(config_id);
isc_throw(NotImplemented,
- "Parser error: Subnet4 parameter not supported: "
+ "parser error: Subnet4 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -586,7 +1477,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- /// @throw Dhcp4ConfigError when requested parameter is not present
+ /// @throw DhcpConfigError when requested parameter is not present
Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -605,7 +1496,7 @@ private:
if (found) {
return (Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -620,8 +1511,14 @@ private:
/// storage for pools belonging to this subnet
PoolStorage pools_;
+ /// storage for options belonging to this subnet
+ OptionStorage options_;
+
/// parsers are stored here
ParserCollection parsers_;
+
+ /// @brief Pointer to the created subnet object.
+ isc::dhcp::Subnet4Ptr subnet_;
};
/// @brief this class parses list of subnets
@@ -629,7 +1526,7 @@ private:
/// This is a wrapper parser that handles the whole list of Subnet4
/// definitions. It iterates over all entries and creates Subnet4ConfigParser
/// for each entry.
-class Subnets4ListConfigParser : public Dhcp4ConfigParser {
+class Subnets4ListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -650,7 +1547,6 @@ public:
// used: Subnet4ConfigParser
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
-
ParserPtr parser(new Subnet4ConfigParser("subnet"));
parser->build(subnet);
subnets_.push_back(parser);
@@ -678,14 +1574,20 @@ public:
/// @brief Returns Subnet4ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets4ListConfigParser object
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets4ListConfigParser(param_name));
}
/// @brief collection of subnet parsers.
ParserCollection subnets_;
+
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -694,15 +1596,17 @@ public:
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv4 parameter
/// @throw NotImplemented if trying to create a parser for unknown config element
-Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["interface"] = InterfaceListConfigParser::Factory;
- factories["subnet4"] = Subnets4ListConfigParser::Factory;
- factories["version"] = StringParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet4"] = Subnets4ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -717,7 +1621,7 @@ Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
}
isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -729,44 +1633,133 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
- try {
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
+ try {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ if (config_pair.first == "subnet4") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
}
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet4");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
+ }
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed:") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+ rollback = true;
+
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ rollback = true;
+
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed:") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
+const std::map<std::string, uint32_t>& getUint32Defaults() {
+ return (uint32_defaults);
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 2ee9cf6..4f1ea32 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.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
@@ -14,6 +14,7 @@
#include <exceptions/exceptions.h>
#include <cc/data.h>
+#include <stdint.h>
#include <string>
#ifndef DHCP4_CONFIG_PARSER_H
@@ -27,117 +28,12 @@ namespace dhcp {
class Dhcpv4Srv;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef std::map<std::string, uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef std::map<std::string, std::string> StringStorage;
-
-/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv4Srv object.
-class Dhcp4ConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- Dhcp4ConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
-/// @brief Base abstract class for all DHCPv4 parsers
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
-class Dhcp4ConfigParser {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
-
- // Private construtor and assignment operator assures that nobody
- // will be able to copy or assign a parser. There are no defined
- // bodies for them.
- Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
- Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- Dhcp4ConfigParser() {}
-public:
- /// The destructor.
- virtual ~Dhcp4ConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp4Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<Dhcp4ConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure DHCPv4 server (\c Dhcpv4Srv) with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
+/// This function parses configuration information stored in @c config_set
+/// and configures the @c server by applying the configuration to it.
/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// derived class implementations of @c DhcpConfigParser meet the assumption,
/// that is, it ensures that either configuration is fully applied or the
/// state of the server is intact.
///
@@ -153,7 +49,8 @@ typedef std::vector<ParserPtr> ParserCollection;
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
///
/// @param config_set a new configuration (JSON) for DHCPv4 server
/// @return answer that contains result of reconfiguration
@@ -161,6 +58,16 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
+
+/// @brief Returns the global uint32_t values storage.
+///
+/// This function must be only used by unit tests that need
+/// to access uint32_t global storage to verify that the
+/// Uint32Parser works as expected.
+///
+/// @return a reference to a global uint32 values storage.
+const std::map<std::string, uint32_t>& getUint32Defaults();
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index eb5d482..435a25e 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv4Srv::establishSession() {
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp4ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index d5066e8..c2b755c 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -34,6 +34,97 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
+ { "item_name": "option-data",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-data",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0
+ },
+ { "item_name": "data",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
+ } ]
+ }
+ },
+
{ "item_name": "subnet4",
"item_type": "list",
"item_optional": false,
@@ -80,9 +171,50 @@
"item_optional": false,
"item_default": ""
}
- }
- ]
- }
+ },
+
+ { "item_name": "option-data",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-data",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0
+ },
+ {
+ "item_name": "data",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
+ } ]
+ }
+ } ]
+ }
}
],
"commands": [
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 994f004..fc47823 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -31,6 +31,15 @@ This critical error message indicates that the initial DHCPv4
configuration has failed. The server will start, but nothing will be
served until the configuration has been corrected.
+% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
+This warning message is issued on an attempt to configure multiple options
+with the same option code for a particular subnet. Adding multiple options
+is uncommon for DHCPv4, but is not prohibited.
+
% DHCP4_CONFIG_UPDATE updated configuration received: %1
A debug message indicating that the IPv4 DHCP server has received an
updated configuration from the BIND 10 configuration system.
@@ -40,16 +49,39 @@ This is a debug message that is issued every time the server receives a
configuration. That happens at start up and also when a server configuration
change is committed by the administrator.
-% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
-This is an informational message reporting that the configuration has
-been extended to include the specified IPv4 subnet.
-
% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
This is an informational message announcing the successful processing of a
-new configuration. it is output during server startup, and when an updated
+new configuration. It is output during server startup, and when an updated
configuration is committed by the administrator. Additional information
may be provided.
+% DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
+This informational message is printed every time DHCPv4 server is started
+and gives both the type and name of the database being used to store
+lease and other information.
+
+% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
+This debug message indicates that the server successfully advertised
+a lease. It is up to the client to choose one server out of othe advertised
+and continue allocation with that server. This is a normal behavior and
+indicates successful operation.
+
+% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
+This message indicates that the server has failed to offer a lease to
+the specified client after receiving a DISCOVER message from it. There are
+many possible reasons for such a failure.
+
+% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
+This debug message indicates that the server successfully granted a lease
+in response to client's REQUEST message. This is a normal behavior and
+incicates successful operation.
+
+% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
+This message indicates that the server failed to grant a lease to the
+specified client after receiving a REQUEST message from it. There are many
+possible reasons for such a failure. Additional messages will indicate the
+reason.
+
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
@@ -70,7 +102,7 @@ may well be a valid DHCP packet, just a type not expected by the server
% DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv4 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
@@ -86,6 +118,43 @@ to be a programming error: please raise a bug report.
% DHCP4_QUERY_DATA received packet type %1, data is <%2>
A debug message listing the data received from the client.
+% DHCP4_RELEASE address %1 belonging to client-id %2, hwaddr %3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP4_RELEASE_EXCEPTION exception %1 while trying to release address %2
+This message is output when an error was encountered during an attempt
+to process a RELEASE message. The error will not affect the client,
+which does not expect any response from the server for RELEASE
+messages. Depending on the nature of problem, it may affect future
+server operation.
+
+% DHCP4_RELEASE_FAIL failed to remove lease for address %1 for duid %2, hwaddr %3
+This error message indicates that the software failed to remove a
+lease from the lease database. It is probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP4_RELEASE_FAIL_NO_LEASE client (client-id %2) tried to release address %1, but there is no lease for such address.
+This warning message is printed when client attempts to release a lease,
+but no such lease is known to the server.
+
+% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID client (client-id %2) tried to release address %1, but it belongs to client (client-id %3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP4_RELEASE_FAIL_WRONG_HWADDR client (client-id %2) tried to release address %1, but sent from a wrong hardware address (%3)
+This warning message indicates that client tried to release an address
+that does belong to it, but the lease information was associated with
+a different hardware address. One possible reason for using different
+hardware address is that a cloned virtual machine was not updated and
+both clones use the same client-id.
+
% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
A debug message listing the data returned to the client.
@@ -126,3 +195,13 @@ processed any command-line switches and is starting.
This is a debug message issued during the IPv4 DHCP server startup.
It lists some information about the parameters with which the server
is running.
+
+% DHCP4_SUBNET_SELECTED the %1 subnet was selected for client assignment
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment. Subnet selection is one of the early
+steps in the processing of incoming client message.
+
+% DHCP4_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src: %1, type: %2
+This warning message is output when a packet was received from a subnet
+for which the DHCPv4 server has not been configured. The most probable
+cause is a misconfiguration of the server.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 59edc01..88ccfea 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -16,9 +16,19 @@
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
#include <dhcp/pkt4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/addr_utilities.h>
using namespace isc;
using namespace isc::asiolink;
@@ -28,26 +38,35 @@ using namespace std;
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
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();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
- IfaceMgr::instance().openSockets4(port);
+ if (port) {
+ // open sockets only if port is non-zero. Port 0 is used
+ // for non-socket related testing.
+ IfaceMgr::instance().openSockets4(port);
+ }
setServerID();
+ // Instantiate LeaseMgr
+ LeaseMgrFactory::create(dbconfig);
+ LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
+ .arg(LeaseMgrFactory::instance().getType())
+ .arg(LeaseMgrFactory::instance().getName());
+
+ // Instantiate allocation engine
+ alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+
} catch (const std::exception &e) {
LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
@@ -167,18 +186,11 @@ Dhcpv4Srv::run() {
void
Dhcpv4Srv::setServerID() {
- /// TODO implement this for real once interface detection (ticket 1237)
- /// is done. Use hardcoded server-id for now.
-
-#if 0
- // uncomment this once ticket 1350 is merged.
- IOAddress srvId("127.0.0.1");
- serverid_ = OptionPtr(
- new Option4AddrLst(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, srvId));
-#endif
+ /// @todo: implement this for real (see ticket #2588)
+ serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ IOAddress(HARDCODED_SERVER_ID)));
}
-
void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setIface(question->getIface());
answer->setIndex(question->getIndex());
@@ -188,9 +200,7 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setHops(question->getHops());
// copy MAC address
- vector<uint8_t> mac(question->getChaddr(),
- question->getChaddr() + Pkt4::MAX_CHADDR_LEN);
- answer->setHWAddr(question->getHtype(), question->getHlen(), mac);
+ answer->setHWAddr(question->getHWAddr());
// relay address
answer->setGiaddr(question->getGiaddr());
@@ -203,21 +213,21 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
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);
+ }
}
void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
OptionPtr opt;
// add Message Type Option (type 53)
- std::vector<uint8_t> tmp;
- tmp.push_back(static_cast<uint8_t>(msg_type));
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
- msg->addOption(opt);
+ msg->setType(msg_type);
// DHCP Server Identifier (type 54)
- opt = OptionPtr
- (new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
- msg->addOption(opt);
+ msg->addOption(getServerID());
// more options will be added here later
}
@@ -237,25 +247,109 @@ void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
msg->addOption(opt);
}
-void Dhcpv4Srv::tryAssignLease(Pkt4Ptr& msg) {
- OptionPtr opt;
+void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+
+ // We need to select a subnet the client is connected in.
+ Subnet4Ptr subnet = selectSubnet(question);
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(serverReceivedPacketName(question->getType()));
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ return;
+ }
- // TODO: Implement actual lease assignment here
- msg->setYiaddr(IOAddress(HARDCODED_LEASE));
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
+ .arg(subnet->toText());
- // IP Address Lease time (type 51)
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
- opt->setUint32(HARDCODED_LEASE_TIME);
- msg->addOption(opt);
- // TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
+ // Get client-id option
+ ClientIdPtr client_id;
+ OptionPtr opt = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+ // client-id is not mandatory in DHCPv4
+
+ IOAddress hint = question->getYiaddr();
+
+ HWAddrPtr hwaddr = question->getHWAddr();
+
+ // "Fake" allocation is processing of DISCOVER message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = (question->getType() == DHCPDISCOVER);
+
+ // Use allocation engine to pick a lease for this client. Allocation engine
+ // will try to honour the hint, but it is just a hint - some other address
+ // may be used instead. If fake_allocation is set to false, the lease will
+ // be inserted into the LeaseMgr as well.
+ Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
+ hint, fake_allocation);
+
+ if (lease) {
+ // We have a lease! Let's set it in the packet and send it back to
+ // the client.
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT:DHCP4_LEASE_ALLOC)
+ .arg(lease->addr_.toText())
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)");
+
+ answer->setYiaddr(lease->addr_);
+
+ // IP Address Lease time (type 51)
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
+ opt->setUint32(lease->valid_lft_);
+ answer->addOption(opt);
+
+ // @todo: include real router information here
+ // Router (type 3)
+ opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
+ answer->addOption(opt);
+
+ // Subnet mask (type 1)
+ answer->addOption(getNetmaskOption(subnet));
+
+ // @todo: send renew timer option (T1, option 58)
+ // @todo: send rebind timer option (T2, option 59)
- // Subnet mask (type 1)
- opt = OptionPtr(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
- msg->addOption(opt);
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure. The only thing left is to insert
+ // status code to pass the sad news to the client.
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT_FAIL:DHCP4_LEASE_ALLOC_FAIL)
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)")
+ .arg(hint.toText());
+
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ }
+}
- // Router (type 3)
- opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
- msg->addOption(opt);
+OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+ uint32_t netmask = getNetmask4(subnet->get().second);
+
+ OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
+ DHO_SUBNET_MASK, netmask));
+
+ return (opt);
}
Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
@@ -266,7 +360,7 @@ Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(offer);
- tryAssignLease(offer);
+ assignLease(discover, offer);
return (offer);
}
@@ -279,13 +373,77 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(ack);
- tryAssignLease(ack);
+ assignLease(request, ack);
return (ack);
}
void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
- /// TODO: Implement this.
+
+ // Try to find client-id
+ ClientIdPtr client_id;
+ OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+
+ try {
+ // Do we have a lease for that particular address?
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getYiaddr());
+
+ if (!lease) {
+ // No such lease - bogus release
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
+ .arg(release->getYiaddr().toText())
+ .arg(release->getHWAddr()->toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)");
+ return;
+ }
+
+ // Does the hardware address match? We don't want one client releasing
+ // second client's leases.
+ if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
+ // @todo: Print hwaddr from lease as part of ticket #2589
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ return;
+ }
+
+ // Does the lease have client-id info? If it has, then check it with what
+ // the client sent us.
+ if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id->toText())
+ .arg(lease->client_id_->toText());
+ return;
+ }
+
+ // Ok, hw and client-id match - let's release the lease.
+ if (LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+
+ // Release successful - we're done here
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ } else {
+
+ // Release failed -
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ }
+ } catch (const isc::Exception& ex) {
+ // Rethrow the exception with a bit more data.
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_EXCEPTION)
+ .arg(ex.what())
+ .arg(release->getYiaddr());
+ }
+
}
void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
@@ -327,3 +485,42 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
}
return (UNKNOWN);
}
+
+Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+
+ // Is this relayed message?
+ IOAddress relay = question->getGiaddr();
+ if (relay.toText() == "0.0.0.0") {
+
+ // Yes: Use relay address to select subnet
+ return (CfgMgr::instance().getSubnet4(relay));
+ } else {
+
+ // No: Use client's address to select subnet
+ return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+ }
+}
+
+void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
+ OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_id) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
+ << "received in " << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case MANDATORY:
+ if (!server_id) {
+ isc_throw(RFCViolation, "Server-id option was expected, but not "
+ " received in message "
+ << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case OPTIONAL:
+ // do nothing here
+ ;
+ }
+}
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index db4b4bc..53401c5 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -18,14 +18,16 @@
#include <dhcp/dhcp4.h>
#include <dhcp/pkt4.h>
#include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/alloc_engine.h>
#include <boost/noncopyable.hpp>
#include <iostream>
namespace isc {
-
namespace dhcp {
+
/// @brief DHCPv4 server service.
///
/// This singleton class represents DHCPv4 server. It contains all
@@ -44,6 +46,14 @@ namespace dhcp {
class Dhcpv4Srv : public boost::noncopyable {
public:
+
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
+
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv4 server.
@@ -54,7 +64,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// for testing purposes.
///
/// @param port specifies port number to listen on
- Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
+ /// @param dbconfig Lease manager configuration string. The default
+ /// of the "memfile" manager is used for testing.
+ Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -82,6 +95,8 @@ class Dhcpv4Srv : public boost::noncopyable {
/// As the operation of the method does not depend on any server state, it
/// is declared static.
///
+ /// @todo: This should be named static Pkt4::getName()
+ ///
/// @param type DHCPv4 packet type
///
/// @return Pointer to "const" string containing the packet name.
@@ -90,6 +105,17 @@ class Dhcpv4Srv : public boost::noncopyable {
static const char* serverReceivedPacketName(uint8_t type);
protected:
+
+ /// @brief verifies if specified packet meets RFC requirements
+ ///
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
+ ///
+ /// @param pkt packet to be checked
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
+
/// @brief Processes incoming DISCOVER and returns response.
///
/// Processes received DISCOVER message and verifies that its sender
@@ -156,11 +182,19 @@ protected:
/// client and assigning it. Options corresponding to the lease
/// are added to specific message.
///
- /// Note: Lease manager is not implemented yet, so this method
- /// used fixed, hardcoded lease.
+ /// @param question DISCOVER or REQUEST message from client
+ /// @param answer OFFER or ACK/NAK message (lease options will be added here)
+ void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
+ /// @brief Attempts to renew received addresses
+ ///
+ /// Attempts to renew existing lease. This typically includes finding a lease that
+ /// corresponds to the received address. If no such lease is found, a status code
+ /// response is generated.
///
- /// @param msg OFFER or ACK message (lease options will be added here)
- void tryAssignLease(Pkt4Ptr& msg);
+ /// @param renew client's message asking for renew
+ /// @param reply server's response (ACK or NAK)
+ void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
/// @brief Appends default options to a message
///
@@ -184,12 +218,33 @@ protected:
// previously stored configuration and no network interfaces available)
void setServerID();
+ /// @brief Selects a subnet for a given client's packet.
+ ///
+ /// @param question client's message
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
+
/// server DUID (to be sent in server-identifier option)
OptionPtr serverid_;
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;
+
+ private:
+
+ /// @brief Constructs netmask option based on subnet4
+ /// @param subnet subnet for which the netmask will be calculated
+ ///
+ /// @return Option that contains netmask information
+ static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
+
+ /// @brief Allocation Engine.
+ /// Pointer to the allocation engine that we are currently using
+ /// It must be a pointer, because we will support changing engines
+ /// during normal operation (e.g. to use different allocators)
+ boost::shared_ptr<AllocEngine> alloc_engine_;
+
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index e601919..c0ebcb9 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -66,13 +66,13 @@ dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp4_unittests_LDADD = $(GTEST_LDADD)
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index a4ccabd..ba14edf 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 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
@@ -17,11 +17,13 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
+#include <config/ccsession.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
-#include <config/ccsession.h>
+#include <dhcp/option4_addrlst.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
+#include <boost/foreach.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
@@ -34,12 +36,6 @@ using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::config;
-namespace isc {
-namespace dhcp {
-extern Uint32Storage uint32_defaults;
-}
-}
-
namespace {
class Dhcp4ParserTest : public ::testing::Test {
@@ -54,7 +50,9 @@ public:
// Checks if global parameter of name have expected_value
void checkGlobalUint32(string name, uint32_t expected_value) {
- Uint32Storage::const_iterator it = uint32_defaults.find(name);
+ const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
+ std::map<std::string, uint32_t>::const_iterator it =
+ uint32_defaults.find(name);
if (it == uint32_defaults.end()) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
@@ -73,9 +71,212 @@ public:
}
~Dhcp4ParserTest() {
+ resetConfiguration();
delete srv_;
};
+ /// @brief Create the simple configuration with single option.
+ ///
+ /// This function allows to set one of the parameters that configure
+ /// option value. These parameters are: "name", "code", "data",
+ /// "csv-format" and "space".
+ ///
+ /// @param param_value string holiding option parameter value to be
+ /// injected into the configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param value.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ std::string createConfigWithOption(const std::string& param_value,
+ const std::string& parameter) {
+ std::map<std::string, std::string> params;
+ if (parameter == "name") {
+ params["name"] = param_value;
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "dhcp-message";
+ params["space"] = param_value;
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "code") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = param_value;
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "data") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
+ }
+ return (createConfigWithOption(params));
+ }
+
+ /// @brief Create simple configuration with single option.
+ ///
+ /// This function creates a configuration for a single option with
+ /// custom values for all parameters that describe the option.
+ ///
+ /// @params params map holding parameters and their values.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+ std::ostringstream stream;
+ stream << "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {";
+ bool first = true;
+ typedef std::pair<std::string, std::string> ParamPair;
+ BOOST_FOREACH(ParamPair param, params) {
+ if (!first) {
+ stream << ", ";
+ } else {
+ // cppcheck-suppress unreadVariable
+ first = false;
+ }
+ if (param.first == "name") {
+ stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
+ } else if (param.first == "code") {
+ stream << "\"code\": " << param.second << "";
+ } else if (param.first == "data") {
+ stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
+ }
+ }
+ stream <<
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ return (stream.str());
+ }
+
+ /// @brief Test invalid option parameter value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param param_value string holding invalid option parameter value
+ /// to be injected into configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param_value (can be any of "name", "code", "data")
+ void testInvalidOptionParam(const std::string& param_value,
+ const std::string& parameter) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(param_value, parameter);
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+ }
+
+ /// @brief Test option against given code and data.
+ ///
+ /// @param option_desc option descriptor that carries the option to
+ /// be tested.
+ /// @param expected_code expected code of the option.
+ /// @param expected_data expected data in the option.
+ /// @param expected_data_len length of the reference data.
+ /// @param extra_data if true extra data is allowed in an option
+ /// after tested data.
+ void testOption(const Subnet::OptionDescriptor& option_desc,
+ uint16_t expected_code, const uint8_t* expected_data,
+ size_t expected_data_len,
+ bool extra_data = false) {
+ // Check if option descriptor contains valid option pointer.
+ ASSERT_TRUE(option_desc.option);
+ // Verify option type.
+ EXPECT_EQ(expected_code, option_desc.option->getType());
+ // We may have many different option types being created. Some of them
+ // have dedicated classes derived from Option class. In such case if
+ // we want to verify the option contents against expected_data we have
+ // to prepare raw buffer with the contents of the option. The easiest
+ // way is to call pack() which will prepare on-wire data.
+ util::OutputBuffer buf(option_desc.option->getData().size());
+ option_desc.option->pack(buf);
+ if (extra_data) {
+ // The length of the buffer must be at least equal to size of the
+ // reference data but it can sometimes be greater than that. This is
+ // because some options carry suboptions that increase the overall
+ // length.
+ ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+ expected_data_len);
+ } else {
+ ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+ expected_data_len);
+ }
+ // Verify that the data is correct. Do not verify suboptions and a header.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+ expected_data_len));
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+
+ try {
+ ElementPtr json = Element::fromJSON(config);
+ status = configureDhcp4Server(*srv_, json);
+ } catch (const std::exception& ex) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. The following configuration was used"
+ << " to reset database: " << std::endl
+ << config << std::endl
+ << " and the following error message was returned:"
+ << ex.what() << std::endl;
+ }
+
+ // status object must not be NULL
+ if (!status) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. Configuration function returned"
+ << " NULL pointer" << std::endl;
+ }
+
+ comment_ = parseAnswer(rcode_, status);
+ // returned value should be 0 (configuration success)
+ if (rcode_ != 0) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. Configuration function returned"
+ << " error code " << rcode_ << std::endl;
+ }
+ }
+
Dhcpv4Srv* srv_;
int rcode_;
@@ -213,9 +414,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
- checkResult(status, 2);
+ checkResult(status, 1);
}
// Goal of this test is to verify if pools can be defined
@@ -248,10 +449,808 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp4ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp4ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp4ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp4 option
+/// space) and that it is allowed to define option in the dhcp4
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 109 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp4 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 109,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(109, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp4' option space groups
+ // standard options and the code 100 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+// Goal of this test is to verify that global option
+// data is configured for the subnet if the subnet
+// configuration does not include options configuration.
+TEST_F(Dhcp4ParserTest, optionDataDefaults) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp4' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 56,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp4.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
+ ASSERT_FALSE(desc3.option);
+}
+
+// Goal of this test is to verify options configuration
+// for a single subnet. In particular this test checks
+// that local options configuration overrides global
+// option setting.
+TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"FF\","
+ " \"csv-format\": False"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+ ASSERT_TRUE(subnet1);
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options1->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+ idx1.equal_range(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t foo_expected[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0A
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
+
+ // Test another subnet in the same way.
+ Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+ ASSERT_TRUE(subnet2);
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options2->size());
+
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+ idx2.equal_range(23);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+ const uint8_t foo2_expected[] = { 0xFF };
+ testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameEmpty) {
+ // Empty option names not allowed.
+ testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameSpaces) {
+ // Spaces in option names not allowed.
+ testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNegative) {
+ // Check negative option code -4. This should fail too.
+ testInvalidOptionParam("-4", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNonUint8) {
+ // The valid option codes are uint16_t values so passing
+ // uint16_t maximum value incremented by 1 should result
+ // in failure.
+ testInvalidOptionParam("257", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeZero) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("0", "code");
+}
+
+// Verify that option data which contains non hexadecimal characters
+// is rejected by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataInvalidChar) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("01020R", "data");
+}
+
+// Verify that option data containins '0x' prefix is rejected
+// by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("0x0102", "data");
+}
+
+// Verify that option data consisting od an odd number of
+// hexadecimal digits is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionDataOddLength) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("123", "data");
+}
+
+// Verify that either lower or upper case characters are allowed
+// to specify the option data.
+TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption("0a0b0C0D", "data");
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0x0A, 0x0B, 0x0C, 0x0D
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+}
+
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp4ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "nis-servers";
+ params["space"] = "dhcp4";
+ // Option code 41 means nis-servers.
+ params["code"] = "41";
+ // Specify option values in a CSV (user friendly) format.
+ params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+ params["csv-format"] = "True";
+
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options =
+ subnet->getOptionDescriptors("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(DHO_NIS_SERVERS);
+ // Expect single option with the code equal to NIS_SERVERS option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option4AddrLst> option_addrs =
+ boost::dynamic_pointer_cast<Option4AddrLst>(option);
+ // If cast is unsuccessful than option returned was of a
+ // differnt type than Option6IA. This is wrong.
+ ASSERT_TRUE(option_addrs);
+
+ // Get addresses from the option.
+ Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+ // Verify that the addresses have been configured correctly.
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("192.0.2.10", addrs[0].toText());
+ EXPECT_EQ("192.0.2.1", addrs[1].toText());
+ EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
/// This test checks if Uint32Parser can really parse the whole range
/// and properly err of out of range values. As we can't call Uint32Parser
/// directly, we are exploiting the fact that it is used to parse global
/// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
ConstElementPtr status;
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index ac6f0bb..bc2246f 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-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
@@ -19,7 +19,11 @@
#include <dhcp/dhcp4.h>
#include <dhcp/option.h>
#include <dhcp4/dhcp4_srv.h>
-
+#include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
#include <fstream>
@@ -33,43 +37,41 @@ using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv4Srv: public Dhcpv4Srv {
- // "naked" DHCPv4 server, exposes internal fields
+ // "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
-
- boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
- return Dhcpv4Srv::processDiscover(discover);
- }
- boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request) {
- return Dhcpv4Srv::processRequest(request);
- }
- void processRelease(boost::shared_ptr<Pkt4>& release) {
- return Dhcpv4Srv::processRelease(release);
- }
- void processDecline(boost::shared_ptr<Pkt4>& decline) {
- Dhcpv4Srv::processDecline(decline);
- }
- boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform) {
- return Dhcpv4Srv::processInform(inform);
- }
+ NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+ using Dhcpv4Srv::processDiscover;
+ using Dhcpv4Srv::processRequest;
+ using Dhcpv4Srv::processRelease;
+ using Dhcpv4Srv::processDecline;
+ using Dhcpv4Srv::processInform;
+ using Dhcpv4Srv::getServerID;
+ using Dhcpv4Srv::sanityCheck;
};
class Dhcpv4SrvTest : public ::testing::Test {
public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes common objects used in many tests.
+ /// Also sets up initial configuration in CfgMgr.
Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
- fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
- if (if_nametoindex("lo") > 0) {
- fakeifaces << "lo 127.0.0.1";
- } else if (if_nametoindex("lo0") > 0) {
- fakeifaces << "lo0 127.0.0.1";
- }
- fakeifaces.close();
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
+ 2000, 3000));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().addSubnet4(subnet_);
}
+ /// @brief checks that the response matches request
+ /// @param q query (client's message)
+ /// @param a answer (server's message)
void MessageCheck(const boost::shared_ptr<Pkt4>& q,
const boost::shared_ptr<Pkt4>& a) {
ASSERT_TRUE(q);
@@ -80,7 +82,7 @@ public:
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
- // check that bare minimum of required options are there
+ // Check that bare minimum of required options are there
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
@@ -90,29 +92,208 @@ public:
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
- // check that something is offered
+ // Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
}
+ /// @brief generates client-id option
+ ///
+ /// Generate client-id option of specified length
+ /// Ids with different lengths are sufficent to generate
+ /// unique ids. If more fine grained control is required,
+ /// tests generate client-ids on their own.
+ /// Sets client_id_ field.
+ /// @param size size of the client-id to be generated
+ OptionPtr generateClientId(size_t size = 4) {
+
+ OptionBuffer clnt_id(size);
+ for (int i = 0; i < size; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ client_id_ = ClientIdPtr(new ClientId(clnt_id));
+
+ return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(),
+ clnt_id.begin() + size)));
+ }
+
+ /// @brief generate hardware address
+ ///
+ /// @param size size of the generated MAC address
+ /// @param pointer to Hardware Address object
+ HWAddrPtr generateHWAddr(size_t size = 6) {
+ const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
+ OptionBuffer mac(size);
+ for (int i = 0; i < size; ++i) {
+ mac[i] = 50 + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+ }
+
+ /// Check that address was returned from proper range, that its lease
+ /// lifetime is correct, that T1 and T2 are returned properly
+ /// @param rsp response to be checked
+ /// @param subnet subnet that should be used to verify assigned address and options
+ /// @param t1_mandatory is T1 mandatory?
+ /// @param t2_mandatory is T2 mandatory?
+ void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
+ bool t1_mandatory = false, bool t2_mandatory = false) {
+
+ // Technically inPool implies inRange, but let's be on the safe
+ // side and check both.
+ EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
+ EXPECT_TRUE(subnet->inPool(rsp->getYiaddr()));
+
+ // Check lease time
+ OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
+ if (!opt) {
+ ADD_FAILURE() << "Lease time option missing in response";
+ } else {
+ EXPECT_EQ(opt->getUint32(), subnet->getValid());
+ }
+
+ // Check T1 timer
+ opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT1());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T1 option missing";
+ }
+ }
+
+ // Check T2 timer
+ opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT2());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T2 option missing";
+ }
+ }
+ }
+
+ /// @brief Basic checks for generated response (message type and trans-id).
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_message_type expected message type
+ /// @param expected_transid expected transaction-id
+ void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ /// @brief Checks if the lease sent to client is present in the database
+ ///
+ /// @param rsp response packet to be validated
+ /// @param client_id expected client-identifier (or NULL)
+ /// @param HWAddr expected hardware address (not used now)
+ /// @param expected_addr expected address
+ Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+ const HWAddrPtr&, const IOAddress& expected_addr) {
+
+ ClientIdPtr id;
+ if (client_id) {
+ OptionBuffer data = client_id->getData();
+ id.reset(new ClientId(data));
+ }
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
+ if (!lease) {
+ cout << "Lease for " << expected_addr.toText()
+ << " not found in the database backend.";
+ return (Lease4Ptr());
+ }
+
+ EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+
+ EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+ if (client_id) {
+ EXPECT_TRUE(*lease->client_id_ == *id);
+ }
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
+ /// @param rsp response packet to be validated
+ /// @param expected_srvid expected value of server-id
+ void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
+ // Check that server included its server-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getType(), expected_srvid->getType() );
+ EXPECT_EQ(opt->len(), expected_srvid->len() );
+ EXPECT_TRUE(opt->getData() == expected_srvid->getData());
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+ /// @param rsp response packet to be validated
+ /// @param expected_clientid expected value of client-id
+ void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(expected_clientid->getType(), opt->getType());
+ EXPECT_EQ(expected_clientid->len(), opt->len());
+ EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+ }
+
~Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
+ CfgMgr::instance().deleteSubnets4();
};
+
+ /// @brief A subnet used in most tests
+ Subnet4Ptr subnet_;
+
+ /// @brief A pool used in most tests
+ Pool4Ptr pool_;
+
+ /// @brief A client-id used in most tests
+ ClientIdPtr client_id_;
};
+// Sanity check. Verifies that both Dhcpv4Srv and its derived
+// class NakedDhcpv4Srv can be instantiated and destroyed.
TEST_F(Dhcpv4SrvTest, basic) {
- // nothing to test. DHCPv4_srv instance is created
- // in test fixture. It is destroyed in destructor
+ // Check that the base class can be instantiated
Dhcpv4Srv* srv = NULL;
ASSERT_NO_THROW({
srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
});
-
delete srv;
+
+ // Check that the derived class can be instantiated
+ NakedDhcpv4Srv* naked_srv = NULL;
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+ delete naked_srv;
+
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(0);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+
+ delete naked_srv;
}
+// Verifies that received DISCOVER can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processDiscover) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = 255 - i;
@@ -127,26 +308,26 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
pkt->setRemoteAddr(IOAddress("192.0.2.56"));
pkt->setGiaddr(IOAddress("192.0.2.67"));
- // let's make it a relayed message
+ // Let's make it a relayed message
pkt->setHops(3);
pkt->setRemotePort(DHCP4_SERVER_PORT);
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
MessageCheck(pkt, offer);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
pkt->setHops(0);
pkt->setGiaddr(IOAddress("0.0.0.0"));
pkt->setRemotePort(DHCP4_CLIENT_PORT);
@@ -155,12 +336,12 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
@@ -169,8 +350,15 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
delete srv;
}
+// Verifies that received REQUEST can be processed correctly,
+// that the ACK message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See RequestBasic.
TEST_F(Dhcpv4SrvTest, processRequest) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
@@ -185,22 +373,22 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
req->setRemoteAddr(IOAddress("192.0.2.56"));
req->setGiaddr(IOAddress("192.0.2.67"));
- // should not throw
+ // Should not throw
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
MessageCheck(req, ack);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
req->setHops(0);
req->setGiaddr(IOAddress("0.0.0.0"));
req->setRemotePort(DHCP4_CLIENT_PORT);
@@ -209,12 +397,12 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
ack = srv->processDiscover(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPOFFER, ack->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
@@ -228,14 +416,11 @@ TEST_F(Dhcpv4SrvTest, processRelease) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processRelease(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
-
delete srv;
}
@@ -244,13 +429,11 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processDecline(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
delete srv;
}
@@ -259,15 +442,15 @@ TEST_F(Dhcpv4SrvTest, processInform) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processInform(pkt);
);
- // should return something
+ // Should return something
EXPECT_TRUE(srv->processInform(pkt));
- // TODO: Implement more reasonable tests before starting
+ // @todo Implement more reasonable tests before starting
// work on processSomething() method.
delete srv;
@@ -305,4 +488,642 @@ TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
}
}
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint, but that hint is invalid
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address (!= hint)
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("10.1.2.3");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.107"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
+// This test checks that the server is offering different addresses to different
+// clients in OFFERs. Please note that OFFER is not a guarantee that such
+// an address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same offer as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. OFFER is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis1 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr dis2 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 2345));
+ Pkt4Ptr dis3 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 3456));
+
+ dis1->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis2->setRemoteAddr(IOAddress("192.0.2.2"));
+ dis3->setRemoteAddr(IOAddress("192.0.2.3"));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ dis1->addOption(clientid1);
+ dis2->addOption(clientid2);
+ dis3->addOption(clientid3);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer1 = srv->processDiscover(dis1);
+ Pkt4Ptr offer2 = srv->processDiscover(dis2);
+ Pkt4Ptr offer3 = srv->processDiscover(dis3);
+
+ // Check if we get response at all
+ checkResponse(offer1, DHCPOFFER, 1234);
+ checkResponse(offer2, DHCPOFFER, 2345);
+ checkResponse(offer3, DHCPOFFER, 3456);
+
+ IOAddress addr1 = offer1->getYiaddr();
+ IOAddress addr2 = offer2->getYiaddr();
+ IOAddress addr3 = offer3->getYiaddr();
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(offer1, subnet_);
+ checkAddressParams(offer2, subnet_);
+ checkAddressParams(offer3, subnet_);
+
+ // Check DUIDs
+ checkServerId(offer1, srv->getServerID());
+ checkServerId(offer2, srv->getServerID());
+ checkServerId(offer3, srv->getServerID());
+ checkClientId(offer1, clientid1);
+ checkClientId(offer2, clientid2);
+ checkClientId(offer3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed a single REQUEST message with:
+// - client-id option
+// - hwaddr information
+// - requested address (that the client received in DISCOVER/OFFER exchange)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address
+//
+// Test verifies that the lease is actually in the database.
+TEST_F(Dhcpv4SrvTest, RequestBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ IOAddress hint("192.0.2.107");
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+ req->setYiaddr(hint);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = checkLease(ack, clientid, req->getHWAddr(), hint);
+
+ ASSERT_TRUE(l);
+ LeaseMgrFactory::instance().deleteLease(l->addr_);
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed 3 REQUEST messages with:
+// - client-id option (differs between messages)
+// - hwaddr information (differs between messages)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address (different for each client)
+TEST_F(Dhcpv4SrvTest, ManyRequests) {
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress req_addr1("192.0.2.105");
+ const IOAddress req_addr2("192.0.2.101");
+ const IOAddress req_addr3("192.0.2.109");
+ const IOAddress relay("192.0.2.1");
+
+ Pkt4Ptr req1 = Pkt4Ptr(new Pkt4(DHCPOFFER, 1234));
+ Pkt4Ptr req2 = Pkt4Ptr(new Pkt4(DHCPOFFER, 2345));
+ Pkt4Ptr req3 = Pkt4Ptr(new Pkt4(DHCPOFFER, 3456));
+
+ req1->setRemoteAddr(relay);
+ req2->setRemoteAddr(relay);
+ req3->setRemoteAddr(relay);
+
+ req1->setYiaddr(req_addr1);
+ req2->setYiaddr(req_addr2);
+ req3->setYiaddr(req_addr3);
+
+ req1->setHWAddr(generateHWAddr(6));
+ req2->setHWAddr(generateHWAddr(7));
+ req3->setHWAddr(generateHWAddr(8));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack1 = srv->processRequest(req1);
+ Pkt4Ptr ack2 = srv->processRequest(req2);
+ Pkt4Ptr ack3 = srv->processRequest(req3);
+
+ // Check if we get response at all
+ checkResponse(ack1, DHCPACK, 1234);
+ checkResponse(ack2, DHCPACK, 2345);
+ checkResponse(ack3, DHCPACK, 3456);
+
+ IOAddress addr1 = ack1->getYiaddr();
+ IOAddress addr2 = ack2->getYiaddr();
+ IOAddress addr3 = ack3->getYiaddr();
+
+ // Check that every client received the address it requested
+ EXPECT_EQ(req_addr1.toText(), addr1.toText());
+ EXPECT_EQ(req_addr2.toText(), addr2.toText());
+ EXPECT_EQ(req_addr3.toText(), addr3.toText());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(ack1, subnet_);
+ checkAddressParams(ack2, subnet_);
+ checkAddressParams(ack3, subnet_);
+
+ // Check DUIDs
+ checkServerId(ack1, srv->getServerID());
+ checkServerId(ack2, srv->getServerID());
+ checkServerId(ack3, srv->getServerID());
+ checkClientId(ack1, clientid1);
+ checkClientId(ack2, clientid2);
+ checkClientId(ack3, clientid3);
+
+ // Check that leases are in the database
+ Lease4Ptr l = checkLease(ack1, clientid1, req1->getHWAddr(), addr1);
+ EXPECT_TRUE(l);
+ l = checkLease(ack2, clientid2, req2->getHWAddr(), addr2);
+ l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+
+// This test verifies that incoming (positive) REQUEST/Renewing can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ // EXPECT_EQ(l->t1_, temp_t1);
+ // EXPECT_EQ(l->t2_, temp_t2);
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+
+ req->addOption(clientid);
+ req->addOption(srv->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// @todo: Implement tests for rejecting renewals
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
+
+ // Empty packet, no server-id
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
+
+ pkt->addOption(srv->getServerID());
+
+ // Server-id is mandatory and present = no exception
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
+
+ // Server-id is forbidden, but present => exception
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
+ RFCViolation);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly.
+// As there is no REPLY in DHCPv4, the only thing to verify here is that
+// the lease is indeed removed from the database.
+TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // The lease should be gone from LeaseMgr
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 0);
+
+ // Try to get it by hw/subnet_id combination
+ l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 0);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Ok, the lease is *really* not there.
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+TEST_F(Dhcpv4SrvTest, ReleaseReject) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t t1 = 50;
+ const uint32_t t2 = 75;
+ const uint32_t valid = 100;
+ const time_t timestamp = time(NULL) - 10;
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t bogus_mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr bogus_hw(new HWAddr(bogus_mac_addr, sizeof(bogus_mac_addr), HTYPE_ETHER));
+ OptionPtr bogus_clientid = generateClientId(7); // different length
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(bogus_hw);
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: Lease is not known to the server");
+
+ // There is nothing to check here. The lease is not there and server does
+ // not send anything back. This case is enumerated here just for keeping
+ // parity with similar test in DHCPv6.
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // CASE 2: Lease is known and belongs to this client, but to a different hardware
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but uses different HW addr");
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ valid, t1, t2, timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ rel->setHWAddr(bogus_hw);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease was not removed (due to hardware address mis-match)
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ rel->setHWAddr(hw); // proper HW address this time
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(bogus_clientid); // but invalid client-id
+
+ OptionPtr x = rel->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Final sanity check. Verify that with valid hw and client-id release is successful
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(clientid);
+
+ // It should work this time
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc
index 0ed61b7..93e836b 100644
--- a/src/bin/dhcp4/tests/dhcp4_unittests.cc
+++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc
@@ -13,13 +13,16 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <log/logger_support.h>
-
+#include <dhcp4/dhcp4_log.h>
#include <gtest/gtest.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the B10_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
isc::log::initLogger();
int result = RUN_ALL_TESTS();
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 1d9766f..decd986 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -59,14 +59,14 @@ if USE_CLANGPP
b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
endif
-b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp6dir = $(pkgdatadir)
b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 4c6fab1..5c8675e 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.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
@@ -19,11 +19,13 @@
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
#include <log/logger_support.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
@@ -38,37 +40,56 @@
#include <stdint.h>
using namespace std;
+using namespace isc;
using namespace isc::data;
+using namespace isc::dhcp;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
-/// @brief an auxiliary type used for storing an element name and its parser
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
-/// @brief a factory method that will create a parser for a given element name
-typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+/// @brief Factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-/// @brief a collection of factories that create parsers for specified element names
+/// @brief Collection of factories that create parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
typedef std::map<string, uint32_t> Uint32Storage;
-/// @brief a collection of elements that store string values
+/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
-/// @brief a collection of pools
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
-typedef std::vector<Pool6Ptr> PoolStorage;
+typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -79,6 +100,10 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+
/// @brief a dummy configuration parser
///
/// This is a debugging parser. It does not configure anything,
@@ -90,7 +115,7 @@ public:
/// @brief Constructor
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -99,7 +124,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -108,12 +133,12 @@ public:
value_ = new_config;
}
- /// @brief pretends to apply the configuration
+ /// @brief Pretends to apply the configuration.
///
/// This is a method required by the base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -125,7 +150,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -137,6 +162,86 @@ private:
ConstElementPtr value_;
};
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -144,10 +249,10 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
///
/// @todo this class should be turned into the template class which
/// will handle all uintX_types of data (see ticket #2415).
@@ -155,18 +260,29 @@ class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
+ ///
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref Uint32Parser::setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
+ /// @throw isc::dhcp::DhcpConfigError if failed to parse
+ /// the configuration parameter as uint32_t value.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
bool parse_error = false;
// Cast the provided value to int64 value to check.
int64_t int64value = 0;
@@ -180,51 +296,45 @@ public:
parse_error = true;
}
if (!parse_error) {
+ // Check that the value is not out of bounds.
if ((int64value < 0) ||
(int64value > std::numeric_limits<uint32_t>::max())) {
parse_error = true;
} else {
- try {
- value_ = boost::lexical_cast<uint32_t>(value->str());
- } catch (const boost::bad_lexical_cast &) {
- parse_error = true;
- }
+ // A value is not out of bounds so let's cast it to
+ // the uint32_t type.
+ value_ = static_cast<uint32_t>(int64value);
}
}
-
+ // Invalid value provided.
if (parse_error) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
+ isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
<< " as unsigned 32-bit integer.");
}
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet6Parser when constructing
- /// the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed uint32_t value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs Uint32Parser objects
+ /// @brief Factory that constructs Uint32Parser objects.
///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ /// @param param_name name of the parameter to be parsed.
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
- /// @param storage pointer to the storage container
+ /// @param storage pointer to the storage container.
void setStorage(Uint32Storage* storage) {
storage_ = storage;
}
@@ -232,10 +342,8 @@ public:
private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
-
/// name of the parameter to be parsed
std::string param_name_;
-
/// the actual parsed value
uint32_t value_;
};
@@ -247,54 +355,60 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using the
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
+ ///
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
+ : storage_(&string_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
- /// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// Parses configuration parameter's value as string.
///
/// @param value pointer to the content of parsed values
+ /// @throws DhcpConfigError if the parsed parameter's name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
value_ = value->str();
boost::erase_all(value_, "\"");
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs StringParser objects
+ /// @brief Factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(StringStorage* storage) {
@@ -302,17 +416,14 @@ public:
}
private:
- /// pointer to the storage, where parsed value will be stored
+ /// Pointer to the storage, where parsed value will be stored
StringStorage* storage_;
-
- /// name of the parameter to be parsed
+ /// Name of the parameter to be parsed
std::string param_name_;
-
- /// the actual parsed value
+ /// The actual parsed value
std::string value_;
};
-
/// @brief parser for interface list definition
///
/// This parser handles Dhcp6/interface entry.
@@ -333,7 +444,7 @@ public:
/// @throw BadValue if supplied parameter name is not "interface"
InterfaceListConfigParser(const std::string& param_name) {
if (param_name != "interface") {
- isc_throw(BadValue, "Internal error. Interface configuration "
+ isc_throw(isc::BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -359,7 +470,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -383,7 +494,7 @@ public:
/// @brief constructor.
PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
+ : pools_(NULL) {
// ignore parameter name, it is always Dhcp6/subnet6[X]/pool
}
@@ -393,11 +504,13 @@ public:
/// No validation is done at this stage, everything is interpreted as
/// interface name.
/// @param pools_list list of pools defined for a subnet
- /// @throw BadValue if storage was not specified (setStorage() not called)
+ /// @throw isc::InvalidOperation if storage was not specified
+ /// (setStorage() not called)
void build(ConstElementPtr pools_list) {
+
// setStorage() should have been called before build
if (!pools_) {
- isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+ isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
" but pool parser asked to parse pools");
}
@@ -433,12 +546,12 @@ public:
// be checked in Pool6 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -451,11 +564,11 @@ public:
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -463,24 +576,29 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(PoolStorage* storage) {
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -490,21 +608,30 @@ private:
/// This is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
+
/// @brief Parser for option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful than an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere util support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
class OptionDataParser : public DhcpConfigParser {
public:
@@ -528,45 +655,53 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp6ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError if invalid parameter specified in
/// the configuration.
/// @throw isc::InvalidOperation if failed to set storage prior to
/// calling build.
- /// @throw isc::BadValue if option data storage is invalid.
virtual void build(ConstElementPtr option_data_entries) {
+
if (options_ == NULL) {
isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
"parsing option data.");
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
- } else if (param.first == "data") {
- boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
if (value_parser) {
- value_parser->setStorage(&string_values_);
+ value_parser->setStorage(&boolean_values_);
parser = value_parser;
}
} else {
- isc_throw(Dhcp6ConfigError,
- "Parser error: option-data parameter not supported: "
+ isc_throw(DhcpConfigError,
+ "parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -582,16 +717,21 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
"commiting option data.");
} else if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is not
// than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
@@ -601,7 +741,7 @@ public:
idx.erase(range.first, range.second);
}
// Append new option to the main storage.
- options_->push_back(option_descriptor_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -629,63 +769,103 @@ private:
/// is intitialized but this check is not needed here because it is done
/// in the \ref build function.
///
- /// @throw Dhcp6ConfigError if parameters provided in the configuration
+ /// @throw DhcpConfigError if parameters provided in the configuration
/// are invalid.
void createOption() {
+
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
// does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getUint32Param("code");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv6.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " exceed " << std::numeric_limits<uint16_t>::max());
}
// Check that the option name has been specified, is non-empty and does not
// contain spaces.
// @todo possibly some more restrictions apply here?
- std::string option_name = getStringParam("name");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
<< " empty");
} else if (option_name.find(" ") != std::string::npos) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V6, option_code);
+
+ } else if (option_space == "dhcp4") {
+ isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
+ << " for DHCPv4 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
- // Get all existing DHCPv6 option definitions. The one that matches
- // our option will be picked and used to create it.
- OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
- // Get search index #1. It allows searching for options definitions
- // using option type value.
- const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
- // Get all option definitions matching option code we want to create.
- const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
- size_t num_defs = std::distance(range.first, range.second);
+
OptionPtr option;
- // Currently we do not allow duplicated definitions and if there are
- // any duplicates we issue internal server error.
- if (num_defs > 1) {
- isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
- << " supported to initialize multiple option definitions"
- << " for the same option code. This will be supported once"
- << " there option spaces are implemented.");
- } else if (num_defs == 0) {
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -699,57 +879,52 @@ private:
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} else {
- // We have exactly one option definition for the particular option code
- // use it to create the option instance.
- const OptionDefinitionPtr& def = *(range.first);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
- OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V6, option_code, data_tokens) :
+ def->optionFactory(Option::V6, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
- << " option definition (code " << option_code << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
<< ex.what());
}
}
- }
-
- /// @brief Get a parameter from the strings storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- std::string getStringParam(const std::string& param_id) const {
- StringStorage::const_iterator param = string_values_.find(param_id);
- if (param == string_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
- }
-
- /// @brief Get a parameter from the uint32 values storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- uint32_t getUint32Param(const std::string& param_id) const {
- Uint32Storage::const_iterator param = uint32_values_.find(param_id);
- if (param == uint32_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
/// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
+ isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -775,7 +950,7 @@ public:
/// for options within a single subnet and creates options' instances.
///
/// @param option_data_list pointer to a list of options' data sets.
- /// @throw Dhcp6ConfigError if option parsing failed.
+ /// @throw DhcpConfigError if option parsing failed.
void build(ConstElementPtr option_data_list) {
BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -815,7 +990,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -830,6 +1005,254 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in the data store.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
@@ -846,6 +1269,8 @@ public:
/// @brief parses parameter value
///
/// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -857,60 +1282,119 @@ public:
// a setStorage and build methods are invoked.
// Try uint32 type parser.
- if (buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second)) {
- // Storage set, build invoked on the parser, proceed with
- // next configuration element.
- continue;
- }
- // Try string type parser.
- if (buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second)) {
- continue;
- }
- // Try pools parser.
- if (buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second)) {
- continue;
- }
- // Try option data parser.
- if (buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- continue;
+ if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+ param.second) &&
+ // Try string type parser.
+ !buildParser<StringParser, StringStorage >(parser, string_values_,
+ param.second) &&
+ // Try pool parser.
+ !buildParser<PoolParser, PoolStorage >(parser, pools_,
+ param.second) &&
+ // Try option data parser.
+ !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+ param.second)) {
+ // Appropriate parsers are created in the createSubnet6ConfigParser
+ // and they should be limited to those that we check here for. Thus,
+ // if we fail to find a matching parser here it is a programming error.
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
- }
- /// @brief commits received configuration.
- ///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet6
- /// objects. Subnet6 are then added to DHCP CfgMgr.
- void commit() {
- // Invoke commit on all sub-data parsers.
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
+ // Create a subnet.
+ createSubnet();
+ }
+
+ /// @brief Adds the created subnet to a server's configuration.
+ void commit() {
+ if (subnet_) {
+ isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
+
+ // Find a subnet string.
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
-
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> pref = getParam("preferred-lifetime");
@@ -924,127 +1408,101 @@ public:
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Create a new subnet.
+ subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Add pools to it.
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool6(*it);
+ subnet_->addPool(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
-
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
-
- CfgMgr::instance().addSubnet6(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
/// @param config_id name od the entry
+ ///
/// @return parser object for specified entry name
- /// @throw NotImplemented if trying to create a parser for unknown config element
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "subnet", StringParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "pool", PoolParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
// Used for debugging only.
// return new DebugParser(config_id);
- isc_throw(NotImplemented,
- "Parser error: Subnet6 parameter not supported: "
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: subnet6 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1052,8 +1510,8 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- /// @throw Dhcp6ConfigError when requested parameter is not present
- Triplet<uint32_t> getParam(const std::string& name) {
+ /// @throw DhcpConfigError when requested parameter is not present
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
Uint32Storage::iterator global = uint32_defaults.find(name);
@@ -1069,9 +1527,9 @@ private:
}
if (found) {
- return (Triplet<uint32_t>(value));
+ return (isc::dhcp::Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+ isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1091,6 +1549,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::Subnet6Ptr subnet_;
};
/// @brief this class parses a list of subnets
@@ -1136,7 +1597,7 @@ public:
// the old one and replace with the new one.
// remove old subnets
- CfgMgr::instance().deleteSubnets6();
+ isc::dhcp::CfgMgr::instance().deleteSubnets6();
BOOST_FOREACH(ParserPtr subnet, subnets_) {
subnet->commit();
@@ -1147,7 +1608,7 @@ public:
/// @brief Returns Subnet6ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets6ListConfigParser object
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets6ListConfigParser(param_name));
}
@@ -1155,6 +1616,11 @@ public:
ParserCollection subnets_;
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1166,25 +1632,15 @@ public:
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "interface", InterfaceListConfigParser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "subnet6", Subnets6ListConfigParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "version", StringParser::Factory));
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet6"] = Subnets6ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1198,23 +1654,8 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
return (f->second(config_id));
}
-/// @brief configures DHCPv6 server
-///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
-///
-/// This method does not throw. It catches all exceptions and returns them as
-/// reconfiguration statuses. It may return the following response codes:
-/// 0 - configuration successful
-/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
-///
-/// @param config_set a new configuration for DHCPv6 server
-/// @return answer that contains result of reconfiguration
-/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -1226,42 +1667,126 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
try {
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ if (config_pair.first == "subnet6") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet6");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
}
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed:") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed:")
+ + ex.what());
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed:") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index ed44bb9..6d7a807 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.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
@@ -27,129 +27,25 @@ namespace dhcp {
class Dhcpv6Srv;
-/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv6Srv object.
-class Dhcp6ConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- Dhcp6ConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
-/// @brief Base abstract class for all DHCPv6 parsers
-///
-/// Each instance of a class derived from this class parses one specific config
-/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
-/// complex (e.g. a subnet). In such case, it is likely that a parser will
-/// spawn child parsers to parse child elements in the configuration.
-/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
-class DhcpConfigParser {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
- DhcpConfigParser(const DhcpConfigParser& source);
- DhcpConfigParser& operator=(const DhcpConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- DhcpConfigParser() {}
-public:
- /// The destructor.
- virtual ~DhcpConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp6Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
+/// @brief Configures DHCPv6 server
///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c Dhcp6ConfigError.
-/// If the given configuration requires resource allocation and it fails,
-/// a corresponding standard exception will be thrown.
-/// Other exceptions may also be thrown, depending on the implementation of
-/// the underlying derived class of \c Dhcp6ConfigError.
-/// In any case the strong guarantee is provided as described above except
-/// in the very rare cases where the \c commit() method of a parser throws
-/// an exception. If that happens this function converts the exception
-/// into a \c FatalError exception and rethrows it. This exception is
-/// expected to be caught at the highest level of the application to terminate
-/// the program gracefully.
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but the values could not be
+/// stored in the configuration).
///
-/// \param server The \c Dhcpv6Srv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
+/// @param server DHCPv6 server object.
+/// @param config_set a new configuration for DHCPv6 server.
+/// @return answer that contains result of the reconfiguration.
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& server,
- isc::data::ConstElementPtr config_set);
+configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 8611365..ba3e2c2 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv6Srv::establishSession() {
try {
configureDhcp6Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp6ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index 5350fd8..fa37769 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -35,19 +35,19 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
+ from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
- are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
- unsigned 32 bit integer), but some are very specialized (e.g. \ref
- isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
- some cases, e.g. subnet6 definitions, the configuration entry is not a simple
- value, but a map or a list itself. In such case, the parser iterates over all
- elements and creates parsers for a given scope. This process may be repeated
- (sort of) recursively.
+ are generic (e.g. Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g.
+ Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
+ e.g. subnet6 definitions, the configuration entry is not a simple value, but
+ a map or a list itself. In such case, the parser iterates over all elements
+ and creates parsers for a given scope. This process may be repeated (sort of)
+ recursively.
@section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
@@ -55,16 +55,16 @@
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
- defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
- isc::dhcp::StringParser) implement that inheritance. By default, they store
- values in global uint32_defaults and string_defaults storages. However, it is
- possible to instruct them to store parsed values in more specific
- storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
- that has its own storage that is unique for each subnet. Finally, during commit
- phase (commit() method), appropriate parsers can use apply parameter inheritance.
+ defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
+ inheritance. By default, they store values in global uint32_defaults and
+ string_defaults storages. However, it is possible to instruct them to store
+ parsed values in more specific storages. That capability is used, e.g. in
+ Subnet6ConfigParser that has its own storage that is unique for each subnet.
+ Finally, during commit phase (commit() method), appropriate parsers can use
+ apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
+ class called DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index c5e9565..b719ebb 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -40,6 +40,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -67,6 +117,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
},
@@ -152,6 +212,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
} ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6ab42b3..83f75d9 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -47,9 +47,9 @@ This is an informational message reporting that the configuration has
been extended to include the specified subnet.
% DHCP6_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
-This warning message is issued on attempt to configure multiple options with the
+This warning message is issued on an attempt to configure multiple options with the
same option code for the particular subnet. Adding multiple options is uncommon
-for DHCPv6, yet it is not prohibited.
+for DHCPv6, but it is not prohibited.
% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
This is a debug message that is issued every time the server receives a
@@ -61,15 +61,22 @@ A debug message indicating that the IPv6 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP6_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
-This informational message is printed every time DHCPv6 is started.
-It indicates what database backend type is being to store lease and
-other information.
+This informational message is printed every time the IPv6 DHCP server
+is started. It indicates what database backend type is being to store
+lease and other information.
+
+% DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
+This error message indicates a database consistency failure. The lease
+database has an entry indicating that the given address is in use,
+but the lease does not contain any client identification. This is most
+likely due to a software error: please raise a bug report. As a temporary
+workaround, manually remove the lease entry from the database.
% DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
This debug message indicates that the server successfully advertised
-a lease. It is up to the client to choose one server out of othe advertised
-and continue allocation with that server. This is a normal behavior and
-indicates successful operation.
+a lease. It is up to the client to choose one server out of the
+advertised servers and continue allocation with that server. This
+is a normal behavior and indicates successful operation.
% DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2
This message indicates that the server failed to advertise (in response to
@@ -79,19 +86,43 @@ such failure. Each specific failure is logged in a separate log entry.
% DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3)
This debug message indicates that the server successfully granted (in
response to client's REQUEST message) a lease. This is a normal behavior
-and incicates successful operation.
+and indicates successful operation.
% DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2
This message indicates that the server failed to grant (in response to
received REQUEST) a lease for a given client. There may be many reasons for
such failure. Each specific failure is logged in a separate log entry.
-% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
-This message indicates that received DHCPv6 packet is invalid. This may be due
-to a number of reasons, e.g. the mandatory client-id option is missing,
-the server-id forbidden in that particular type of message is present,
-there is more than one instance of client-id or server-id present,
-etc. The exact reason for rejecting the packet is included in the message.
+% DHCP6_RELEASE address %1 belonging to client duid=%2, iaid=%3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP6_RELEASE_FAIL failed to remove lease for address %1 for duid=%2, iaid=%3
+This error message indicates that the software failed to remove a
+lease from the lease database. It probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP6_RELEASE_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to client (duid=%3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
@@ -128,7 +159,7 @@ a received OFFER packet as UNKNOWN).
% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv6 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP6_PACKET_SEND_FAIL failed to send DHCPv6 packet: %1
@@ -149,6 +180,13 @@ as a hint for possible requested address.
% DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3
A debug message listing the data received from the client or relay.
+% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
+This message indicates that received DHCPv6 packet is invalid. This may be due
+to a number of reasons, e.g. the mandatory client-id option is missing,
+the server-id forbidden in that particular type of message is present,
+there is more than one instance of client-id or server-id present,
+etc. The exact reason for rejecting the packet is included in the message.
+
% DHCP6_RESPONSE_DATA responding with packet type %1 data is %2
A debug message listing the data returned to the client.
@@ -216,3 +254,8 @@ recently and does not recognize its well-behaving clients. This is more
probable if you see many such messages. Clients will recover from this,
but they will most likely get a different IP addresses and experience
a brief service interruption.
+
+% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
+This warning message is printed when client attempts to release a lease,
+but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
+possible reasons for such behavior.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index a50be03..18acd76 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.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
@@ -32,6 +32,7 @@
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
@@ -340,8 +341,8 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
// Get the list of options configured for a subnet.
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to match requested options with those configured for a subnet.
// If match is found, append configured option to the answer message.
BOOST_FOREACH(uint16_t opt, requested_opts) {
@@ -404,8 +405,8 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
switch (serverid) {
case FORBIDDEN:
- if (server_ids.size() > 0) {
- isc_throw(RFCViolation, "Exactly 1 server-id option expected, but "
+ if (!server_ids.empty()) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
<< server_ids.size() << " received in " << pkt->getName());
}
break;
@@ -436,6 +437,8 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(question);
@@ -450,7 +453,10 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// perhaps this should be logged on some higher level? This is most likely
// configuration bug.
- LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(question->getName());
+
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
.arg(subnet->toText());
@@ -604,7 +610,7 @@ OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
- ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
"Sorry, no known leases for this duid/iaid."));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
@@ -640,6 +646,8 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(renew);
@@ -688,11 +696,176 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
break;
}
}
+}
+
+void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+
+ // We need to release addresses for all IA_NA options in the client's
+ // RELEASE message.
+ // @todo Add support for IA_TA
+ // @todo Add support for IA_PD
+ // @todo Consider supporting more than one address in a single IA_NA.
+ // That was envisaged by RFC3315, but it never happened. The only
+ // software that supports that is Dibbler, but its author seriously doubts
+ // if anyone is really using it. Clients that want more than one address
+ // just include more instances of IA_NA options.
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ // see sanityCheck() called from processRelease()
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
+ .arg(release->getRemoteAddr().toText());
+
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ int general_status = STATUS_Success;
+ for (Option::OptionCollection::iterator opt = release->options_.begin();
+ opt != release->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ // @todo: add support for IA_PD
+ // @todo: add support for IA_TA
+ default:
+ // remaining options are stateless and thus ignored in this context
+ ;
+ }
+ }
+
+ // To be pedantic, we should also include status code in the top-level
+ // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
+ // This behavior will likely go away in RFC3315bis.
+ reply->addOption(createStatusCode(general_status,
+ "Summary status for all processed IA_NAs"));
+}
+
+OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia) {
+ // Release can be done in one of two ways:
+ // Approach 1: extract address from client's IA_NA and see if it belongs
+ // to this particular client.
+ // Approach 2: find a subnet for this client, get a lease for
+ // this subnet/duid/iaid and check if its content matches to what the
+ // client is asking us to release.
+ //
+ // This method implements approach 1.
+
+ // That's our response
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ if (!release_addr) {
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "You did not include address in your RELEASE"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());
+
+ if (!lease) {
+ // client releasing a lease that we don't know about.
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "Sorry, no known leases for this duid/iaid, can't release."));
+ general_status = STATUS_NoBinding;
+
+ LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
+ .arg(duid->toText())
+ .arg(ia->getIAID());
+
+ return (ia_rsp);
+ }
+
+ if (!lease->duid_) {
+ // Something is gravely wrong here. We do have a lease, but it does not
+ // have mandatory DUID information attached. Someone was messing with our
+ // database.
+
+ LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
+ .arg(release_addr->getAddress().toText());
+
+ general_status = STATUS_UnspecFail;
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Database consistency check failed when trying to RELEASE"));
+ return (ia_rsp);
+ }
+
+ if (*duid != *(lease->duid_)) {
+ // Sorry, it's not your address. You can't release it.
+ LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->duid_->toText());
+ general_status = STATUS_NoBinding;
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This address does not belong to you, you can't release it"));
+ return (ia_rsp);
+ }
+
+ if (ia->getIAID() != lease->iaid_) {
+ // This address belongs to this client, but to a different IA
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->iaid_)
+ .arg(ia->getIAID());
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This is your address, but you used wrong IAID"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+ // It is not necessary to check if the address matches as we used
+ // getLease6(addr) method that is supposed to return a proper lease.
+
+ // Ok, we've passed all checks. Let's release this address.
+
+ if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Server failed to release a lease"));
+
+ LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+ general_status = STATUS_UnspecFail;
+
+ return (ia_rsp);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+
+ ia_rsp->addOption(createStatusCode(STATUS_Success,
+ "Lease released. Thank you, please come again."));
+
+ return (ia_rsp);
+ }
}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
sanityCheck(solicit, MANDATORY, FORBIDDEN);
@@ -751,8 +924,16 @@ Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
}
Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
- /// @todo: Implement this
+
+ sanityCheck(release, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
+
+ copyDefaultOptions(release, reply);
+ appendDefaultOptions(release, reply);
+
+ releaseLeases(release, reply);
+
return reply;
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b6b0306..30abb50 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -31,22 +31,6 @@
namespace isc {
namespace dhcp {
-/// An exception that is thrown if a DHCPv6 protocol violation occurs while
-/// processing a message (e.g. a mandatory option is missing)
-class RFCViolation : public isc::Exception {
-public:
-
-/// @brief constructor
-///
-/// @param file name of the file, where exception occurred
-/// @param line line of the file, where exception occurred
-/// @param what text description of the issue that caused exception
-RFCViolation(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-
-
/// @brief DHCPv6 server service.
///
/// This class represents DHCPv6 server. It contains all
@@ -83,7 +67,7 @@ public:
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
Dhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
@@ -212,17 +196,39 @@ protected:
/// @brief Renews specific IA_NA option
///
- /// Generates response to IA_NA. This typically includes finding a lease that
- /// corresponds to the received address. If no such lease is found, an IA_NA
- /// response is generated with an appropriate status code.
+ /// Generates response to IA_NA in Renew. This typically includes finding a
+ /// lease that corresponds to the received address. If no such lease is
+ /// found, an IA_NA response is generated with an appropriate status code.
///
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
/// @param question client's message
/// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+ /// @brief Releases specific IA_NA option
+ ///
+ /// Generates response to IA_NA in Release message. This covers finding and
+ /// removal of a lease that corresponds to the received address. If no such
+ /// lease is found, an IA_NA response is generated with an appropriate
+ /// status code.
+ ///
+ /// As RFC 3315 requires that a single status code be sent for the whole message,
+ /// this method may update the passed general_status: it is set to SUCCESS when
+ /// message processing begins, but may be updated to some error code if the
+ /// release process fails.
+ ///
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param general_status a global status (it may be updated in case of errors)
+ /// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
+ OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia);
+
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -271,6 +277,17 @@ protected:
/// @param reply server's response
void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+ /// @brief Attempts to release received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to release
+ /// received addresses. If no such leases are found, or the lease fails
+ /// proper checks (e.g. belongs to someone else), a proper status
+ /// code is added to reply message. Released addresses are not added
+ /// to REPLY packet, just its IA_NA containers.
+ /// @param release client's message asking to release
+ /// @param reply server's response
+ void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
@@ -291,7 +308,7 @@ private:
boost::shared_ptr<AllocEngine> alloc_engine_;
/// Server DUID (to be sent in server-identifier option)
- boost::shared_ptr<isc::dhcp::Option> serverid_;
+ OptionPtr serverid_;
/// Indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index b01b877..d251df3 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -63,14 +63,13 @@ dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp6_unittests_LDADD = $(GTEST_LDADD)
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 4fd6baa..b6ef97f 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 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
@@ -53,10 +53,20 @@ public:
resetConfiguration();
};
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data" and
+ /// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -67,21 +77,41 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["code"] = "80";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "subscriber-id";
+ params["space"] = param_value;
+ params["code"] = "38";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
- params["code"] = "80";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
- std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+ std::string createConfigWithOption(const std::map<std::string,
+ std::string>& params)
+ {
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
@@ -97,14 +127,19 @@ public:
if (!first) {
stream << ", ";
} else {
+ // cppcheck-suppress unreadVariable
first = false;
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
- stream << "\"code\": " << param.second << "";
+ stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -130,6 +165,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -144,14 +180,14 @@ public:
<< ex.what() << std::endl;
}
-
- // returned value should be 0 (configuration success)
+ // status object must not be NULL
if (!status) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
<< " NULL pointer" << std::endl;
}
comment_ = parseAnswer(rcode_, status);
+ // returned value should be 0 (configuration success)
if (rcode_ != 0) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
@@ -215,9 +251,10 @@ public:
ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
expected_data_len);
}
- // Verify that the data is correct. However do not verify suboptions.
+ // Verify that the data is correct. Do not verify suboptions and a header.
const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
- EXPECT_TRUE(memcmp(expected_data, data, expected_data_len));
+ EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+ expected_data_len));
}
Dhcpv6Srv srv_;
@@ -370,11 +407,12 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
- EXPECT_EQ(2, rcode_);
+
+ EXPECT_EQ(1, rcode_);
}
// Goal of this test is to verify if pools can be defined
@@ -411,6 +449,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space) and that it is allowed to define option in the dhcp6
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 100 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp6 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp6' option space groups
+ // standard options and the code 3 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 3,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -421,14 +816,18 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": True"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -445,42 +844,112 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
// Check that options with other option codes are not returned.
- for (uint16_t code = 102; code < 110; ++code) {
+ for (uint16_t code = 47; code < 57; ++code) {
range = idx.equal_range(code);
EXPECT_EQ(0, std::distance(range.first, range.second));
}
}
-// Goal of this test is to verify options configuration
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp6' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 38,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp6.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
+ ASSERT_FALSE(desc3.option);
+}
+
+// The goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
@@ -491,22 +960,28 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -520,33 +995,35 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
}
// Goal of this test is to verify options configuration
@@ -561,18 +1038,22 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"0102030405060708090A\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
" \"pool\": [ \"2001:db8:2::/80\" ],"
" \"subnet\": \"2001:db8:2::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"FFFEFDFCFB\""
+ " \"name\": \"user-class\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 15,"
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -586,43 +1067,45 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet1);
- const Subnet::OptionContainer& options1 = subnet1->getOptions();
- ASSERT_EQ(1, options1.size());
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range1 =
- idx1.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A
};
// Check if option is valid in terms of code and carried data.
- testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
// Test another subnet in the same way.
Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
ASSERT_TRUE(subnet2);
- const Subnet::OptionContainer& options2 = subnet2->getOptions();
- ASSERT_EQ(1, options2.size());
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options2->size());
- const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
- idx2.equal_range(101);
+ idx2.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
- const uint8_t foo2_expected[] = {
+ const uint8_t user_class_expected[] = {
0xFF, 0xFE, 0xFD, 0xFC, 0xFB
};
- testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
}
// Verify that empty option name is rejected in the configuration.
@@ -704,25 +1187,26 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(80);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x0A, 0x0B, 0x0C, 0x0D
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
}
// Verify that specific option object is returned for standard
@@ -730,10 +1214,12 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
TEST_F(Dhcp6ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
- params["name"] = "OPTION_IA_NA";
+ params["name"] = "ia-na";
+ params["space"] = "dhcp6";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
- params["data"] = "ABCDEF01 02030405 06070809";
+ params["data"] = "12345, 6789, 1516";
+ params["csv-format"] = "True";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
@@ -745,11 +1231,11 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -774,9 +1260,9 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
// has been set correctly.
- EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
- EXPECT_EQ(0x02030405, optionIA->getT1());
- EXPECT_EQ(0x06070809, optionIA->getT2());
+ EXPECT_EQ(12345, optionIA->getIAID());
+ EXPECT_EQ(6789, optionIA->getT1());
+ EXPECT_EQ(1516, optionIA->getT2());
}
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 040db35..dee3de5 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -29,6 +29,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
@@ -59,6 +60,7 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
@@ -71,8 +73,9 @@ public:
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
2000, 3000, 4000));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
+ CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet_);
}
@@ -143,11 +146,14 @@ public:
}
// Checks that server rejected IA_NA, i.e. that it has no addresses and
- // that expected status code really appears there.
+ // that expected status code really appears there. In some limited cases
+ // (reply to RELEASE) it may be used to verify positive case, where
+ // IA_NA response is expected to not include address.
+ //
// Status code indicates type of error encountered (in theory it can also
// indicate success, but servers typically don't send success status
// as this is the default result and it saves bandwidth)
- void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+ void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
uint16_t expected_status_code) {
// Make sure there is no address assigned.
EXPECT_FALSE(ia->getOption(D6O_IAADDR));
@@ -158,6 +164,12 @@ public:
boost::shared_ptr<OptionCustom> status =
boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
EXPECT_TRUE(status);
if (status) {
@@ -169,6 +181,26 @@ public:
}
}
+
+ void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -338,25 +370,27 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"pool\": [ \"2001:db8:1::/64\" ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"option-data\": [ {"
- " \"name\": \"OPTION_DNS_SERVERS\","
+ " \"name\": \"dns-servers\","
+ " \"space\": \"dhcp6\","
" \"code\": 23,"
- " \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
- "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+ " \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
+ " \"csv-format\": True"
" },"
" {"
- " \"name\": \"OPTION_FOO\","
- " \"code\": 1000,"
- " \"data\": \"1234\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+ NakedDhcpv6Srv srv(0);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
@@ -369,23 +403,23 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+ boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
- // We have not requested option with code 1000 so it should not
+ // We have not requested any options so they should not
// be included in the response.
- ASSERT_FALSE(adv->getOption(1000));
+ ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
- // Let's now request option with code 1000.
- // We expect that server will include this option in its reply.
+ // Let's now request some options. We expect that the server
+ // will include them in its response.
boost::shared_ptr<OptionIntArray<uint16_t> >
option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
// Create vector with two option codes.
std::vector<uint16_t> codes(2);
- codes[0] = 1000;
+ codes[0] = D6O_SUBSCRIBER_ID;
codes[1] = D6O_NAME_SERVERS;
// Pass this code to option.
option_oro->setValues(codes);
@@ -393,7 +427,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(option_oro);
// Need to process SOLICIT again after requesting new option.
- adv = srv->processSolicit(sol);
+ adv = srv.processSolicit(sol);
ASSERT_TRUE(adv);
OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -410,7 +444,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// There is a dummy option with code 1000 we requested from a server.
// Expect that this option is in server's response.
- tmp = adv->getOption(1000);
+ tmp = adv->getOption(D6O_SUBSCRIBER_ID);
ASSERT_TRUE(tmp);
// Check that the option contains valid data (from configuration).
@@ -444,8 +478,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -454,7 +487,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -467,7 +500,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -487,8 +520,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -505,7 +537,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -521,7 +553,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -541,8 +573,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -557,7 +588,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -571,20 +602,22 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
// This test checks that the server is offering different addresses to different
// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
-// and address will be assigned. Had the pool was very small and contained only
+// an address will be assigned. Had the pool was very small and contained only
// 2 addresses, the third client would get the same advertise as the first one
// and this is a correct behavior. It is REQUEST that will fail for the third
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManySolicits) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
@@ -608,9 +641,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
sol3->addOption(clientid3);
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processSolicit(sol1);
- Pkt6Ptr reply2 = srv->processSolicit(sol2);
- Pkt6Ptr reply3 = srv->processSolicit(sol3);
+ Pkt6Ptr reply1 = srv.processSolicit(sol1);
+ Pkt6Ptr reply2 = srv.processSolicit(sol2);
+ Pkt6Ptr reply3 = srv.processSolicit(sol3);
// check if we get response at all
checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
@@ -631,9 +664,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -663,8 +696,7 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, RequestBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a REQUEST
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
@@ -681,10 +713,10 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
req->addOption(clientid);
// server-id is mandatory in REQUEST
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRequest(req);
+ Pkt6Ptr reply = srv.processRequest(req);
// check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -700,7 +732,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// check that the lease is really in the database
@@ -717,8 +749,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManyRequests) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
@@ -742,14 +773,14 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
req3->addOption(clientid3);
// server-id is mandatory in REQUEST
- req1->addOption(srv->getServerID());
- req2->addOption(srv->getServerID());
- req3->addOption(srv->getServerID());
+ req1->addOption(srv.getServerID());
+ req2->addOption(srv.getServerID());
+ req3->addOption(srv.getServerID());
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processRequest(req1);
- Pkt6Ptr reply2 = srv->processRequest(req2);
- Pkt6Ptr reply3 = srv->processRequest(req3);
+ Pkt6Ptr reply1 = srv.processRequest(req1);
+ Pkt6Ptr reply2 = srv.processRequest(req2);
+ Pkt6Ptr reply3 = srv.processRequest(req3);
// check if we get response at all
checkResponse(reply1, DHCPV6_REPLY, 1234);
@@ -770,9 +801,9 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -796,8 +827,7 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
// - returned REPLY message has IA that includes IAADDR
// - lease is actually renewed in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::cafe:babe");
const uint32_t iaid = 234;
@@ -838,10 +868,10 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRenew(req);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -857,7 +887,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
// Check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// Check that the lease is really in the database
@@ -892,9 +922,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
// - returned REPLY message has IA that includes STATUS-CODE
// - No lease in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewReject) {
-
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::dead");
const uint32_t transid = 1234;
@@ -922,12 +950,12 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Case 1: No lease known to server
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRenew(req);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, transid);
@@ -936,7 +964,7 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// Check that there is no lease added
l = LeaseMgrFactory::instance().getLease6(addr);
@@ -953,14 +981,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// Pass it to the server and hope for a REPLY
- reply = srv->processRenew(req);
+ reply = srv.processRenew(req);
checkResponse(reply, DHCPV6_REPLY, transid);
tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE(tmp);
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// There is a iaid mis-match, so server should respond that there is
// no such address to renew.
@@ -972,14 +1000,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(generateClientId(13)); // generate different DUID
// (with length 13)
- reply = srv->processRenew(req);
+ reply = srv.processRenew(req);
checkResponse(reply, DHCPV6_REPLY, transid);
tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE(tmp);
// Check that IA_NA was returned and that there's an address included
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
- checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
lease = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_TRUE(lease);
@@ -989,10 +1017,198 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ checkIA_NAStatusCode(ia, STATUS_Success);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
+ EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: No lease known to server");
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Finally, let's cleanup the database
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
// This test verifies if the status code option is generated properly.
TEST_F(Dhcpv6SrvTest, StatusCode) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// a dummy content for client-id
uint8_t expected[] = {
@@ -1002,7 +1218,7 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
};
// Create the option.
- OptionPtr status = srv->createStatusCode(3, "ABCDE");
+ OptionPtr status = srv.createStatusCode(3, "ABCDE");
// Allocate an output buffer. We will store the option
// in wire format here.
OutputBuffer buf(sizeof(expected));
@@ -1016,34 +1232,34 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
// This test verifies if the sanityCheck() really checks options presence.
TEST_F(Dhcpv6SrvTest, sanityCheck) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
- // check that the packets originating from local addresses can be
+ // Set link-local sender address, so appropriate subnet can be
+ // selected for this packet.
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
// client-id is optional for information-request, so
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
// empty packet, no client-id, no server-id
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
RFCViolation);
// This doesn't make much sense, but let's check it for completeness
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
OptionPtr clientid = generateClientId();
pkt->addOption(clientid);
// client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
- pkt->addOption(srv->getServerID());
+ pkt->addOption(srv.getServerID());
// both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
// sane section ends here, let's do some negative tests as well
@@ -1051,13 +1267,13 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
pkt->addOption(clientid);
// with more than one client-id it should throw, no matter what
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
pkt->delOption(D6O_CLIENTID);
@@ -1066,20 +1282,21 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
// again we have only one client-id
// let's try different type of insanity - several server-ids
- pkt->addOption(srv->getServerID());
- pkt->addOption(srv->getServerID());
+ pkt->addOption(srv.getServerID());
+ pkt->addOption(srv.getServerID());
// with more than one server-id it should throw, no matter what
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
-
-
}
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
} // end of anonymous namespace
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 286abba..41e280a 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,4 +1,5 @@
/b10-loadzone
+/b10-loadzone.py
/loadzone.py
/run_loadzone.sh
/b10-loadzone.8
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index d181503..8c55f9b 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -67,7 +67,7 @@
<para>
Some control entries (aka directives) are supported.
- $ORIGIN is followed by a domain name, and sets the the origin
+ $ORIGIN is followed by a domain name, and sets the origin
that will be used for relative domain names in subsequent records.
$INCLUDE is followed by a filename to load.
The previous origin is restored after the file is included.
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
index 294df55..0cd68db 100755
--- a/src/bin/loadzone/loadzone.py.in
+++ b/src/bin/loadzone/loadzone.py.in
@@ -200,29 +200,6 @@ class LoadZoneRunner:
logger.info(LOADZONE_SQLITE3_USING_DEFAULT_CONFIG, default_db_file)
return '{"database_file": "' + default_db_file + '"}'
- def __cancel_create(self):
- '''sqlite3-only hack: delete the zone just created on load failure.
-
- This should eventually be done via generic datasrc API, but right now
- we don't have that interface. Leaving the zone in this situation
- is too bad, so we handle it with a workaround.
-
- '''
- if self._datasrc_type is not 'sqlite3':
- return
-
- import sqlite3 # we need the module only here
- import json
-
- # If we are here, the following should basically succeed; since
- # this is considered a temporary workaround we don't bother to catch
- # and recover rare failure cases.
- dbfile = json.loads(self._datasrc_config)['database_file']
- with sqlite3.connect(dbfile) as conn:
- cur = conn.cursor()
- cur.execute("DELETE FROM zones WHERE name = ?",
- [self._zone_name.to_text()])
-
def _report_progress(self, loaded_rrs):
'''Dump the current progress report to stdout.
@@ -274,10 +251,10 @@ class LoadZoneRunner:
self.__loaded_rrs >= self._report_interval):
sys.stdout.write('\n')
except Exception as ex:
- # release any remaining lock held in the client/loader
- loader, datasrc_client = None, None
+ # release any remaining lock held in the loader
+ loader = None
if created:
- self.__cancel_create()
+ datasrc_client.delete_zone(self._zone_name)
logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
self._zone_class)
raise LoadFailure(str(ex))
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 43b7920..b3d61d3 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -18,7 +18,7 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -32,5 +32,13 @@ fi
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
+# For bind10_config
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+
+# For data source loadable modules
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
exec ${LOADZONE_PATH}/b10-loadzone "$@"
diff --git a/src/bin/loadzone/tests/correct/include.db b/src/bin/loadzone/tests/correct/include.db
index a9eeca3..53871bb 100644
--- a/src/bin/loadzone/tests/correct/include.db
+++ b/src/bin/loadzone/tests/correct/include.db
@@ -1,8 +1,6 @@
$ORIGIN include. ; initialize origin
$TTL 300
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.include. hostmaster.include. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/known.test.out b/src/bin/loadzone/tests/correct/known.test.out
index eec692e..377158d 100644
--- a/src/bin/loadzone/tests/correct/known.test.out
+++ b/src/bin/loadzone/tests/correct/known.test.out
@@ -80,6 +80,6 @@ ns5.example.com. 90 IN A 4.4.4.4
comment.example.com. 60 IN SOA ns1.example.com. hostmaster.example.com. 1 43200 900 1814400 7200
comment.example.com. 60 IN NS ns1.example.com.
comment.example.com. 60 IN TXT "Simple text"
-comment.example.com. 60 IN TXT "; No comment"
+comment.example.com. 60 IN TXT "\; No comment"
comment.example.com. 60 IN TXT "Also no comment here"
-comment.example.com. 60 IN TXT "A combination ; see?"
+comment.example.com. 60 IN TXT "A combination \; see?"
diff --git a/src/bin/loadzone/tests/correct/mix1.db b/src/bin/loadzone/tests/correct/mix1.db
index 5bc0a95..059fde7 100644
--- a/src/bin/loadzone/tests/correct/mix1.db
+++ b/src/bin/loadzone/tests/correct/mix1.db
@@ -1,7 +1,5 @@
$ORIGIN mix1.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.mix1. hostmaster.mix1. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/mix2.db b/src/bin/loadzone/tests/correct/mix2.db
index e43b943..e89c2af 100644
--- a/src/bin/loadzone/tests/correct/mix2.db
+++ b/src/bin/loadzone/tests/correct/mix2.db
@@ -1,7 +1,5 @@
$ORIGIN mix2.
-; this needs #2500
-;@ 1 IN SOA ns hostmaster (
-@ 1 IN SOA ns.mix2. hostmaster.mix2. (
+@ 1 IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttl1.db b/src/bin/loadzone/tests/correct/ttl1.db
index fec0813..7f04ff8 100644
--- a/src/bin/loadzone/tests/correct/ttl1.db
+++ b/src/bin/loadzone/tests/correct/ttl1.db
@@ -1,7 +1,5 @@
$ORIGIN ttl1.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.ttl1. hostmaster.ttl1. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttl2.db b/src/bin/loadzone/tests/correct/ttl2.db
index 4705978..b7df040 100644
--- a/src/bin/loadzone/tests/correct/ttl2.db
+++ b/src/bin/loadzone/tests/correct/ttl2.db
@@ -1,7 +1,5 @@
$ORIGIN ttl2.
-; this needs #2500
-;@ 1 IN SOA ns hostmaster (
-@ 1 IN SOA ns.ttl2. hostmaster.ttl2 (
+@ 1 IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8c83b0..844f452 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -1,7 +1,5 @@
$ORIGIN ttlext.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.ttlext. hostmaster.ttlext. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 4244d07..5f377b1 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -5,10 +5,16 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-msgq
CLEANFILES = b10-msgq msgq.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
man_MANS = b10-msgq.8
DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) msgq.xml
+EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
if GENERATE_DOCS
@@ -23,6 +29,11 @@ $(man_MANS):
endif
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py : msgq_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/msgq_messages.mes
+
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-msgq: msgq.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index bd13a1c..6937600 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -31,10 +31,16 @@ import select
import random
from optparse import OptionParser, OptionValueError
import isc.util.process
+import isc.log
+from isc.log_messages.msgq_messages import *
import isc.cc
isc.util.process.rename()
+logger = isc.log.Logger("msgq")
+TRACE_START = logger.DBGLVL_START_SHUT
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
@@ -51,11 +57,11 @@ class SubscriptionManager:
"""Add a subscription."""
target = ( group, instance )
if target in self.subscriptions:
- print("[b10-msgq] Appending to existing target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_APPEND_TARGET, group, instance)
if socket not in self.subscriptions[target]:
self.subscriptions[target].append(socket)
else:
- print("[b10-msgq] Creating new target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_NEW_TARGET, group, instance)
self.subscriptions[target] = [ socket ]
def unsubscribe(self, group, instance, socket):
@@ -162,9 +168,7 @@ class MsgQ:
def setup_listener(self):
"""Set up the listener socket. Internal function."""
- if self.verbose:
- sys.stdout.write("[b10-msgq] Setting up socket at %s\n" %
- self.socket_file)
+ logger.debug(TRACE_BASIC, MSGQ_LISTENER_SETUP, self.socket_file)
self.listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -179,8 +183,7 @@ class MsgQ:
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
self.listen_socket.close()
- sys.stderr.write("[b10-msgq] failed to setup listener on %s: %s\n"
- % (self.socket_file, str(e)))
+ logger.fatal(MSGQ_LISTENER_FAILED, self.socket_file, e)
raise e
if self.poller:
@@ -197,8 +200,7 @@ class MsgQ:
self.setup_poller()
self.setup_listener()
- if self.verbose:
- sys.stdout.write("[b10-msgq] Listening\n")
+ logger.debug(TRACE_START, MSGQ_LISTENER_STARTED);
self.runnable = True
@@ -226,7 +228,7 @@ class MsgQ:
def process_socket(self, fd):
"""Process a read on a socket."""
if not fd in self.sockets:
- sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
+ logger.error(MSGQ_READ_UNKNOWN_FD, fd)
return
sock = self.sockets[fd]
# sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
@@ -243,7 +245,7 @@ class MsgQ:
del self.sockets[fd]
if fd in self.sendbuffs:
del self.sendbuffs[fd]
- sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
+ logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
def getbytes(self, fd, sock, length):
"""Get exactly the requested bytes, or raise an exception if
@@ -285,15 +287,15 @@ class MsgQ:
try:
routing, data = self.read_packet(fd, sock)
except MsgQReceiveError as err:
+ logger.error(MSGQ_RECV_ERR, fd, err)
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Receive error: %s\n" % err)
return
try:
routingmsg = isc.cc.message.from_wire(routing)
except DecodeError as err:
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Routing decode error: %s\n" % err)
+ logger.error(MSGQ_HDR_DECODE_ERR, fd, err)
return
self.process_command(fd, sock, routingmsg, data)
@@ -301,9 +303,7 @@ class MsgQ:
def process_command(self, fd, sock, routing, data):
"""Process a single command. This will split out into one of the
other functions."""
- # TODO: A print statement got removed here (one that prints the
- # routing envelope). When we have logging with multiple levels,
- # we might want to re-add that on a high debug verbosity.
+ logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
cmd = routing["type"]
if cmd == 'send':
self.process_command_send(sock, routing, data)
@@ -319,7 +319,7 @@ class MsgQ:
elif cmd == 'stop':
self.stop()
else:
- sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
+ logger.error(MSGQ_INVALID_CMD, cmd)
def preparemsg(self, env, msg = None):
if type(env) == dict:
@@ -363,8 +363,8 @@ class MsgQ:
elif e.errno in [ errno.EPIPE,
errno.ECONNRESET,
errno.ENOBUFS ]:
- print("[b10-msgq] " + errno.errorcode[e.errno] +
- " on send, dropping message and closing connection")
+ logger.error(MSGQ_SEND_ERR, sock.fileno(),
+ errno.errorcode[e.errno])
self.kill_socket(sock.fileno(), sock)
return None
else:
@@ -491,7 +491,7 @@ class MsgQ:
if err.args[0] == errno.EINTR:
events = []
else:
- sys.stderr.write("[b10-msgq] Error with poll(): %s\n" % err)
+ logger.fatal(MSGQ_POLL_ERR, err)
break
for (fd, event) in events:
if fd == self.listen_socket.fileno():
@@ -502,7 +502,7 @@ class MsgQ:
elif event & select.POLLIN:
self.process_socket(fd)
else:
- print("[b10-msgq] Error: Unknown even in run_poller()")
+ logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
def run_kqueue(self):
while self.running:
@@ -563,18 +563,25 @@ if __name__ == "__main__":
help="UNIX domain socket file the msgq daemon will use")
(options, args) = parser.parse_args()
+ # Init logging, according to the parameters.
+ # FIXME: Do proper logger configuration, this is just a hack
+ # This is #2582
+ sev = 'INFO'
+ if options.verbose:
+ sev = 'DEBUG'
+ isc.log.init("b10-msgq", buffer=False, severity=sev, debuglevel=99)
+
signal.signal(signal.SIGTERM, signal_handler)
# Announce startup.
- if options.verbose:
- sys.stdout.write("[b10-msgq] %s\n" % VERSION)
+ logger.debug(TRACE_START, MSGQ_START, VERSION)
msgq = MsgQ(options.msgq_socket_file, options.verbose)
try:
msgq.setup()
except Exception as e:
- sys.stderr.write("[b10-msgq] Error on startup: %s\n" % str(e))
+ logger.fatal(MSGQ_START_FAIL, e)
sys.exit(1)
try:
diff --git a/src/bin/msgq/msgq.xml b/src/bin/msgq/msgq.xml
index fed4c27..43c72a4 100644
--- a/src/bin/msgq/msgq.xml
+++ b/src/bin/msgq/msgq.xml
@@ -111,7 +111,7 @@
<listitem><para>
The UNIX domain socket file this daemon will use.
The default is
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+ <filename>/usr/local/var/bind10/msg_socket</filename>.
<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
</para></listitem>
</varlistentry>
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
new file mode 100644
index 0000000..21c5aa8
--- /dev/null
+++ b/src/bin/msgq/msgq_messages.mes
@@ -0,0 +1,88 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the ddns messages python module.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MSGQ_HDR_DECODE_ERR Error decoding header received from socket %1: %2
+The socket with mentioned file descriptor sent a packet. However, it was not
+possible to decode the routing header of the packet. The packet is ignored.
+This may be caused by a programmer error (one of the components sending invalid
+data) or possibly by incompatible version of msgq and the component (but that's
+unlikely, as the protocol is not changed often).
+
+% MSGQ_LISTENER_FAILED Failed to initialize listener on socket file '%1': %2
+The message queue daemon tried to listen on a file socket (the path is in the
+message), but it failed. The error from the operating system is logged.
+
+% MSGQ_LISTENER_SETUP Starting to listen on socket file '%1'
+Debug message. The listener is trying to open a listening socket.
+
+% MSGQ_LISTENER_STARTED Successfully started to listen
+Debug message. The message queue successfully opened a listening socket and
+waits for incoming connections.
+
+% MSGQ_POLL_ERR Error while polling for events: %1
+A low-level error happened when waiting for events, the error is logged. The
+reason for this varies, but it usually means the system is short on some
+resources.
+
+% MSGQ_POLL_UNKNOWN_EVENT Got an unknown event from the poller for fd %1: %2
+An unknown event got out from the poll() system call. This should generally not
+happen and it is either a programmer error or OS bug. The event is ignored. The
+number noted as the event is the raw encoded value, which might be useful to
+the authors when figuring the problem out.
+
+% MSGQ_READ_UNKNOWN_FD Got read on strange socket %1
+The OS reported a file descriptor is ready to read. But the daemon doesn't know
+the mentioned file descriptor, which is either a programmer error or OS bug.
+The read event is ignored.
+
+% MSGQ_RECV_ERR Error reading from socket %1: %2
+There was a low-level error when reading from a socket. The error is logged and
+the corresponding socket is dropped.
+
+% MSGQ_RECV_HDR Received header: %1
+Debug message. This message includes the whole routing header of a packet.
+
+% MSGQ_INVALID_CMD Received invalid command: %1
+An unknown command listed in the log has been received. It is ignored. This
+indicates either a programmer error (eg. a typo in the command name) or
+incompatible version of a module and message queue daemon.
+
+% MSGQ_SEND_ERR Error while sending to socket %1: %2
+There was a low-level error when sending data to a socket. The error is logged
+and the corresponding socket is dropped.
+
+% MSGQ_SOCK_CLOSE Closing socket fd %1
+Debug message. Closing the mentioned socket.
+
+% MSGQ_START Msgq version %1 starting
+Debug message. The message queue is starting up.
+
+% MSGQ_START_FAIL Error during startup: %1
+There was an error during early startup of the daemon. More concrete error is
+in the log. The daemon terminates as a result.
+
+% MSGQ_SUBS_APPEND_TARGET Appending to existing target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription by appending it to already existing
+data structure.
+
+% MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription. Also creating a new data structure
+to hold it.
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index 3e464be..3ab4024 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,9 +20,25 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/log/.libs
export PYTHONPATH
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index 50b218b..c9ef5d3 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -21,6 +21,8 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/msgq \
BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/msgq/tests/msgq_test.in b/src/bin/msgq/tests/msgq_test.in
deleted file mode 100755
index e3aae89..0000000
--- a/src/bin/msgq/tests/msgq_test.in
+++ /dev/null
@@ -1,28 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010 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.
-
-PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
-export PYTHON_EXEC
-
-MYPATH_PATH=@abs_top_srcdir@/src/bin/msgq/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/lib/python
-
-export PYTHONPATH
-
-cd ${MYPATH_PATH}
-exec ${PYTHON_EXEC} -O msgq_test.py $*
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 4060190..417418f 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -10,6 +10,7 @@ import errno
import threading
import isc.cc
import collections
+import isc.log
#
# Currently only the subscription part and some sending is implemented...
@@ -457,4 +458,6 @@ class SendNonblock(unittest.TestCase):
if __name__ == '__main__':
+ isc.log.init("b10-msgq")
+ isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index aca8fb2..485d022 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>February 28, 2012</date>
+ <date>August 16, 2012</date>
</refentryinfo>
<refmeta>
@@ -148,6 +148,8 @@ once that is merged you can for instance do 'config add Resolver/forward_address
address or special keyword.
The <varname>key</varname> is a TSIG key name.
The default configuration accepts queries from 127.0.0.1 and ::1.
+ The default action is REJECT for newly added
+ <varname>query_acl</varname> items.
</para>
<para>
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 17b7671..28d6ac9 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -103,7 +103,7 @@
<refsect1>
<title>FILES</title>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd.spec</filename>
+ <filename>/usr/local/share/bind10/stats-httpd.spec</filename>
<!--TODO: The filename should be computed from prefix-->
— the spec file of <command>b10-stats-httpd</command>. This file
contains configurable settings
@@ -115,17 +115,17 @@
how to configure the settings.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xml.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XML document.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xsd.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xsd.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XSD document.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xsl.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xsl.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XSL document.
</para>
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 88a01ed..ee89ad2 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -210,7 +210,7 @@
<refsect1>
<title>FILES</title>
- <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+ <para><filename>/usr/local/share/bind10/stats.spec</filename>
<!--TODO: The filename should be computed from prefix-->
— This is a spec file for <command>b10-stats</command>. It
contains commands for <command>b10-stats</command>. They can be
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index b7fe056..99c5e1e 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -60,7 +60,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1234 3600 1800 2419200 7200')
soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
soa_rrset.add_rdata(soa_rdata)
@@ -68,7 +68,7 @@ soa_rrset.add_rdata(soa_rdata)
# SOA intended to be used for the current SOA at the secondary side.
# Note that its serial is smaller than that of soa_rdata.
begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1230 3600 1800 2419200 7200')
begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
begin_soa_rrset.add_rdata(begin_soa_rdata)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 837cafa..770a8b2 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -24,7 +24,7 @@ The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
"same" (not only for the serial), but it is still not clear what the
receiver should do if this condition does not hold. There was a discussion
-about this at the IETF dnsext wg:
+about this at the IETF dnsext working group:
http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
and the general feeling seems that it would be better to reject the
transfer if a mismatch is detected. On the other hand, also as noted
@@ -61,10 +61,10 @@ There was an error opening a connection to the master. The error is
shown in the log message.
% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
-In an attempt of IXFR processing, the begenning SOA of the first difference
+In an attempt of IXFR processing, the beginning SOA of the first difference
(following the initial SOA that specified the final SOA for all the
differences) was found. This means a connection for xfrin tried IXFR
-and really aot a response for incremental updates.
+and really got a response for incremental updates.
% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
Non incremental transfer was detected at the "first data" of a transfer,
@@ -149,16 +149,16 @@ daemon will now shut down.
The AXFR transfer of the given zone was successful.
The provided information contains the following values:
-messages: Number of overhead DNS messages in the transfer
+messages: Number of overhead DNS messages in the transfer.
records: Number of Resource Records in the full transfer, excluding the
final SOA record that marks the end of the AXFR.
bytes: Full size of the transfer data on the wire.
-run time: Time (in seconds) the complete axfr took
+run time: Time (in seconds) the complete axfr took.
-bytes/second: Transfer speed
+bytes/second: Transfer speed.
% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
An attempt to start a transfer with TSIG was made, but the configured TSIG
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 6e3f4a8..774187f 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -249,7 +249,7 @@ class TestXfroutSessionBase(unittest.TestCase):
# In the RDATA only the serial matters.
for i in range(0, num_soa):
soa.add_rdata(Rdata(RRType.SOA(), soa_class,
- 'm r ' + str(ixfr) + ' 1 1 1 1'))
+ 'm. r. ' + str(ixfr) + ' 1 1 1 1'))
msg.add_rrset(Message.SECTION_AUTHORITY, soa)
renderer = MessageRenderer()
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 97b7a31..d48aa24 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -70,7 +70,7 @@ AXFR-style IXFR.
% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
The requested zone in IXFR was not found in the data source
-even though the xfrout daemon sucessfully found the SOA RR of the zone
+even though the xfrout daemon successfully found the SOA RR of the zone
in the data source. This can happen if the administrator removed the
zone from the data source within the small duration between these
operations, but it's more likely to be a bug or broken data source.
@@ -84,9 +84,6 @@ NOTAUTH.
An IXFR request was received, but the client's SOA version is the same as
or newer than that of the server. The xfrout server responds to the
request with the answer section being just one SOA of that version.
-Note: as of this wrting the 'newer version' cannot be identified due to
-the lack of support for the serial number arithmetic. This will soon
-be implemented.
% XFROUT_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
There was a problem in the lower level module handling configuration and
@@ -206,7 +203,7 @@ xfrout daemon process is still running. This xfrout daemon (the one
printing this message) will not start.
% XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
-Pre-response check for an incomding XFR request failed unexpectedly.
+Pre-response check for an incoming XFR request failed unexpectedly.
The most likely cause of this is that some low level error in the data
source, but it may also be other general (more unlikely) errors such
as memory shortage. Some detail of the error is also included in the
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 8d9b87d..4a37087 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -308,7 +308,7 @@ std::string
str_from_stringstream(std::istream &in, const std::string& file, const int line,
int& pos) throw (JSONError)
{
- char c = 0;
+ char c;
std::stringstream ss;
c = in.get();
++pos;
@@ -390,7 +390,7 @@ number_from_stringstream(std::istream &in, int& pos) {
// value is larger than an int can handle)
ElementPtr
from_stringstream_number(std::istream &in, int &pos) {
- long int i = 0;
+ long int i;
double d = 0.0;
bool is_double = false;
char *endptr;
diff --git a/src/lib/config/config_data.cc b/src/lib/config/config_data.cc
index ebe51cc..fb5dd75 100644
--- a/src/lib/config/config_data.cc
+++ b/src/lib/config/config_data.cc
@@ -235,7 +235,7 @@ ConfigData::getItemList(const std::string& identifier, bool recurse) const {
ConstElementPtr
ConfigData::getFullConfig() const {
ElementPtr result = Element::createMap();
- ConstElementPtr items = getItemList("", true);
+ ConstElementPtr items = getItemList("", false);
BOOST_FOREACH(ConstElementPtr item, items->listValue()) {
result->set(item->stringValue(), getValue(item->stringValue()));
}
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 1be7c7a..e40600d 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -110,11 +110,11 @@ public:
isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
bool recurse = false) const;
- /// Returns all current configuration settings (both non-default and default).
+ /// Returns a map of the top-level configuration items, as currently
+ /// set or their defaults
+ ///
/// \return An ElementPtr pointing to a MapElement containing
- /// string->value elements, where the string is the
- /// full identifier of the configuration option and the
- /// value is an ElementPtr with the value.
+ /// the top-level configuration items
isc::data::ConstElementPtr getFullConfig() const;
private:
@@ -126,6 +126,6 @@ private:
}
#endif
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/config/tests/config_data_unittests.cc b/src/lib/config/tests/config_data_unittests.cc
index 26a3fc6..4b83e5c 100644
--- a/src/lib/config/tests/config_data_unittests.cc
+++ b/src/lib/config/tests/config_data_unittests.cc
@@ -118,7 +118,7 @@ TEST(ConfigData, getLocalConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("{ }", cd.getLocalConfig()->str());
-
+
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
EXPECT_EQ("{ \"item1\": 2 }", cd.getLocalConfig()->str());
@@ -141,12 +141,15 @@ TEST(ConfigData, getFullConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
- EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value1\": \"a\" } }", cd.getFullConfig()->str());
+ ElementPtr my_config3 = Element::fromJSON("{ \"item6\": { \"value2\": 123 } }");
+ cd.setLocalConfig(my_config3);
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value2\": 123 } }", cd.getFullConfig()->str());
}
diff --git a/src/lib/datasrc/client.cc b/src/lib/datasrc/client.cc
index 919e9ab..c69b23c 100644
--- a/src/lib/datasrc/client.cc
+++ b/src/lib/datasrc/client.cc
@@ -28,20 +28,22 @@ namespace datasrc {
ZoneIteratorPtr
DataSourceClient::getIterator(const isc::dns::Name&, bool) const {
- isc_throw(isc::NotImplemented,
- "Data source doesn't support iteration");
+ isc_throw(isc::NotImplemented, "Data source doesn't support iteration");
}
unsigned int
DataSourceClient::getZoneCount() const {
- isc_throw(isc::NotImplemented,
- "Data source doesn't support getZoneCount");
+ isc_throw(isc::NotImplemented, "Data source doesn't support getZoneCount");
}
bool
DataSourceClient::createZone(const dns::Name&) {
- isc_throw(isc::NotImplemented,
- "Data source doesn't support addZone");
+ isc_throw(isc::NotImplemented, "Data source doesn't support createZone");
+}
+
+bool
+DataSourceClient::deleteZone(const dns::Name&) {
+ isc_throw(isc::NotImplemented, "Data source doesn't support deleteZone");
}
} // end namespace datasrc
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 8739489..607af05 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -385,9 +385,41 @@ public:
/// direct zone creation.
/// \throw DataSourceError If something goes wrong in the data source
/// while creating the zone.
- /// \param name The (fully qualified) name of the zone to create
+ /// \param zone_name The (fully qualified) name of the zone to create
/// \return True if the zone was added, false if it already existed
- virtual bool createZone(const dns::Name& name);
+ virtual bool createZone(const dns::Name& zone_name);
+
+ /// \brief Delete a zone from the data source
+ ///
+ /// This method also checks if the specified zone exists in the data
+ /// source, and returns true/false depending on whether the zone
+ /// existed/not existed, respectively. In either case, on successful
+ /// return it ensures the data source does not contain the specified
+ /// name of the zone.
+ ///
+ /// \note This is a tentative API, and this method is likely to change
+ /// or be removed in the near future. For that reason, it currently
+ /// provides a default implementation that throws NotImplemented.
+ /// Note also that this method does not delete other database records
+ /// related to the zone, such as zone's resource records or differences
+ /// corresponding to updates made in the zone. This is primarily for
+ /// implementation simplicity (in the currently intended usage there
+ /// wouldn't be such other data at the time of this call anyway) and due
+ /// to the fact that details of managing zones is still in flux. Once
+ /// the design in this area is fixed we may revisit the behavior.
+ ///
+ /// Apart from the two exceptions mentioned below, in theory this
+ /// call can throw anything, depending on the implementation of
+ /// the datasource backend.
+ ///
+ /// \throw NotImplemented If the datasource backend does not support
+ /// direct zone deletion.
+ /// \throw DataSourceError If something goes wrong in the data source
+ /// while deleting the zone.
+ /// \param zone_name The (fully qualified) name of the zone to be deleted
+ /// \return true if the zone previously existed and has been deleted by
+ /// this method; false if the zone didn't exist.
+ virtual bool deleteZone(const dns::Name& zone_name);
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 0a3957d..0b010a4 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -118,13 +118,25 @@ DatabaseClient::findZone(const Name& name) const {
}
bool
-DatabaseClient::createZone(const Name& name) {
+DatabaseClient::createZone(const Name& zone_name) {
TransactionHolder transaction(*accessor_);
- std::pair<bool, int> zone(accessor_->getZone(name.toText()));
+ const std::pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
if (zone.first) {
return (false);
}
- accessor_->addZone(name.toText());
+ accessor_->addZone(zone_name.toText());
+ transaction.commit();
+ return (true);
+}
+
+bool
+DatabaseClient::deleteZone(const Name& zone_name) {
+ TransactionHolder transaction(*accessor_);
+ const std::pair<bool, int> zinfo(accessor_->getZone(zone_name.toText()));
+ if (!zinfo.first) { // if it doesn't exist just return false
+ return (false);
+ }
+ accessor_->deleteZone(zinfo.second);
transaction.commit();
return (true);
}
@@ -1807,7 +1819,7 @@ public:
arg(zone_).arg(rrclass_).arg(accessor_->getDBName());
return (rrset);
} catch (const Exception& ex) {
- LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADR_BADDATA).
+ LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADER_BADDATA).
arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
arg(begin_).arg(end_).arg(ex.what());
isc_throw(DataSourceError, "Failed to create RRset from diff on "
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 9db8a8f..3302adf 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -155,7 +155,7 @@ public:
///
/// It is empty, but needs a virtual one, since we will use the derived
/// classes in polymorphic way.
- virtual ~DatabaseAccessor() { }
+ virtual ~DatabaseAccessor() {}
/// \brief Retrieve a zone identifier
///
@@ -164,8 +164,8 @@ public:
/// apex), as the DatabaseClient will loop trough the labels itself and
/// find the most suitable zone.
///
- /// It is not specified if and what implementation of this method may throw,
- /// so code should expect anything.
+ /// It is not specified if and what implementation of this method may
+ /// throw, so code should expect anything.
///
/// \param name The (fully qualified) domain name of the zone's apex to be
/// looked up.
@@ -195,6 +195,23 @@ public:
/// or was created by this call).
virtual int addZone(const std::string& name) = 0;
+ /// \brief Delete a zone from the database
+ ///
+ /// Like for deleteRecordToZone, implementations are not required to
+ /// check for the existence of the given zone name, it is the
+ /// responsibility of the caller to do so.
+ ///
+ /// Callers must also start a transaction before calling this method.
+ /// Implementations should throw InvalidOperation if this has not been
+ /// done. Callers should also expect DataSourceError for other potential
+ /// problems specific to the database.
+ ///
+ /// \note This method does not delete other database records related to
+ /// the zone. See \c DataSourceClient::deleteZone for the rationale.
+ ///
+ /// \param zone_id The ID of the zone, that would be returned by getZone().
+ virtual void deleteZone(int zone_id) = 0;
+
/// \brief This holds the internal context of ZoneIterator for databases
///
/// While the ZoneIterator implementation from DatabaseClient does all the
@@ -212,15 +229,15 @@ public:
/// \brief Destructor
///
/// Virtual destructor, so any descendand class is destroyed correctly.
- virtual ~IteratorContext() { }
+ virtual ~IteratorContext() {}
/// \brief Function to provide next resource record
///
/// This function should provide data about the next resource record
/// from the data that is searched. The data is not converted yet.
///
- /// Depending on how the iterator was constructed, there is a difference
- /// in behaviour; for a 'full zone iterator', created with
+ /// Depending on how the iterator was constructed, there is a
+ /// difference in behaviour; for a 'full zone iterator', created with
/// getAllRecords(), all COLUMN_COUNT elements of the array are
/// overwritten.
/// For a 'name iterator', created with getRecords(), the column
@@ -1399,7 +1416,9 @@ public:
/// does not, creates it, commits, and returns true. If the zone
/// does exist already, it does nothing (except abort the transaction)
/// and returns false.
- virtual bool createZone(const isc::dns::Name& name);
+ virtual bool createZone(const isc::dns::Name& zone_name);
+
+ virtual bool deleteZone(const isc::dns::Name& zone_name);
/// \brief Get the zone iterator
///
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index e9b4c90..6983459 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -144,7 +144,7 @@ instead.
% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
The domain name does not have any RRs associated with it, so it doesn't
exist in the database. However, it has a subdomain, so it does exist
-in the DNS address space. This type of domain is known an an "empty
+in the DNS address space. This type of domain is known as an "empty
non-terminal" and so we return NXRRSET instead of NXDOMAIN.
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
@@ -202,7 +202,7 @@ a zone's difference sequences from a database-based data source. The
zone's name and class, database name, and the start and end serials
are shown in the message.
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+% DATASRC_DATABASE_JOURNALREADER_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
This is an error message indicating that a zone's diff is broken and
the data source library failed to convert it to a valid RRset. The
most likely cause of this is that someone has manually modified the
@@ -287,7 +287,7 @@ matching the name. This is returned as the result of the search.
The given wildcard matches the name being sough but it as an empty
nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
-namespace but has no RRs assopciated with it). This will produce NXRRSET.
+namespace but has no RRs associated with it). This will produce NXRRSET.
% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
@@ -490,7 +490,7 @@ destroyed.
% DATASRC_MEM_WILDCARD_CANCEL wildcard match canceled for '%1'
Debug information. A domain above wildcard was reached, but there's something
below the requested domain. Therefore the wildcard doesn't apply here. This
-behaviour is specified by RFC 1034, section 4.3.3
+behaviour is specified by RFC 1034, section 4.3.3.
% DATASRC_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
The software refuses to load DNAME records into a wildcard domain. It isn't
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 4ef49d0..bd71544 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -79,7 +79,8 @@ enum StatementID {
DEL_ZONE_NSEC3_RECORDS = 20,
DEL_NSEC3_RECORD = 21,
ADD_ZONE = 22,
- NUM_STATEMENTS = 23
+ DELETE_ZONE = 23,
+ NUM_STATEMENTS = 24
};
const char* const text_statements[NUM_STATEMENTS] = {
@@ -165,7 +166,9 @@ const char* const text_statements[NUM_STATEMENTS] = {
"AND rdtype=?3 AND rdata=?4",
// ADD_ZONE: add a zone to the zones table
- "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)" // ADD_ZONE
+ "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)", // ADD_ZONE
+ // DELETE_ZONE: delete a zone from the zones table
+ "DELETE FROM zones WHERE id=?1" // DELETE_ZONE
};
struct SQLite3Parameters {
@@ -643,6 +646,19 @@ SQLite3Accessor::addZone(const std::string& name) {
return (getzone_result.second);
}
+void
+SQLite3Accessor::deleteZone(int zone_id) {
+ // Transaction should have been started by the caller
+ if (!dbparameters_->in_transaction) {
+ isc_throw(InvalidOperation, "performing deleteZone on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor proc(*dbparameters_, DELETE_ZONE, "delete zone");
+ proc.bindInt(1, zone_id);
+ proc.exec();
+}
+
namespace {
// Conversion to plain char
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index c5773d0..d014193 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -139,6 +139,10 @@ public:
/// \return the id of the zone that has been added
virtual int addZone(const std::string& name);
+ // Nothing special to add for this implementation (the base class
+ // description is sufficient).
+ virtual void deleteZone(int zone_id);
+
/// \brief Look up all resource records for a name
///
/// This implements the getRecords() method from DatabaseAccessor
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 94d90b5..9f5ade4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -55,6 +55,7 @@ namespace {
// Imaginary zone IDs used in the mock accessor below.
const int READONLY_ZONE_ID = 42;
+const int NEW_ZONE_ID = 420;
const int WRITABLE_ZONE_ID = 4200;
// Commonly used test data
@@ -257,26 +258,43 @@ const char* TEST_NSEC3_RECORDS[][5] = {
*/
class NopAccessor : public DatabaseAccessor {
public:
- NopAccessor() : database_name_("mock_database")
- { }
+ NopAccessor() : database_name_("mock_database") {
+ zones_["example.org."] = READONLY_ZONE_ID;
+ zones_["null.example.org."] = 13;
+ zones_["empty.example.org."] = 0;
+ zones_["bad.example.org."] = -1;
+ }
virtual std::pair<bool, int> getZone(const std::string& name) const {
- if (name == "example.org.") {
- return (std::pair<bool, int>(true, READONLY_ZONE_ID));
- } else if (name == "null.example.org.") {
- return (std::pair<bool, int>(true, 13));
- } else if (name == "empty.example.org.") {
- return (std::pair<bool, int>(true, 0));
- } else if (name == "bad.example.org.") {
- return (std::pair<bool, int>(true, -1));
+ std::map<std::string, int>::const_iterator found = zones_.find(name);
+ if (found != zones_.end()) {
+ return (std::pair<bool, int>(true, found->second));
} else {
return (std::pair<bool, int>(false, 0));
}
}
- virtual int addZone(const std::string&) {
- isc_throw(isc::NotImplemented,
- "This database datasource can't add zones");
+ // A simple implementation of addZone.
+ virtual int addZone(const std::string& zone_name) {
+ if (zone_name == "example.com.") {
+ zones_[zone_name] = NEW_ZONE_ID;
+ }
+
+ // for simplicity we assume zone_name is in zones_ at this point
+ return (zones_[zone_name]);
+ }
+
+ // A simple implementation of deleteZone.
+ virtual void deleteZone(int zone_id) {
+ std::map<std::string, int>::iterator it = zones_.begin();
+ std::map<std::string, int>::iterator end = zones_.end();
+ while (it != end) {
+ if (it->second == zone_id) {
+ zones_.erase(it);
+ return;
+ }
+ ++it;
+ }
}
virtual boost::shared_ptr<DatabaseAccessor> clone() {
@@ -337,7 +355,7 @@ public:
private:
const std::string database_name_;
-
+ std::map<std::string, int> zones_;
};
/**
@@ -438,7 +456,7 @@ public:
// Check any attempt of multiple transactions
if (did_transaction_) {
- isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
+ isc_throw(DataSourceError, "MockAccessor::startTransaction() "
"called multiple times - likely a bug in the test");
}
@@ -447,6 +465,14 @@ public:
did_transaction_ = true;
}
+ // If the test needs multiple calls to startTransaction() and knows it's
+ // safe, it can use this method to disable the safeguard check in
+ // startTransaction(); the test can also use this method by emulating a
+ // lock conflict by setting is_allowed to false.
+ void allowMoreTransaction(bool is_allowed) {
+ did_transaction_ = !is_allowed;
+ }
+
private:
class DomainIterator : public IteratorContext {
public:
@@ -1293,6 +1319,17 @@ public:
}
}
+ // Mock-only; control whether to allow subsequent transaction.
+ void allowMoreTransaction(bool is_allowed) {
+ if (is_mock_) {
+ // Use a separate variable for MockAccessor&; some compilers
+ // would be confused otherwise.
+ MockAccessor& mock_accessor =
+ dynamic_cast<MockAccessor&>(*current_accessor_);
+ mock_accessor.allowMoreTransaction(is_allowed);
+ }
+ }
+
// Some tests only work for MockAccessor. We remember whether our accessor
// is of that type.
bool is_mock_;
@@ -4110,53 +4147,88 @@ TYPED_TEST(DatabaseClientTest, createZone) {
zone(this->client_->findZone(new_name));
ASSERT_EQ(result::NOTFOUND, zone.code);
- // The mock implementation does not do createZone,
- // in which case it should throw NotImplemented (from
- // the base class)
- if (this->is_mock_) {
- ASSERT_THROW(this->client_->createZone(new_name), isc::NotImplemented);
- } else {
- // But in the real case, it should work and return true
- ASSERT_TRUE(this->client_->createZone(new_name));
- const DataSourceClient::FindResult
- zone2(this->client_->findZone(new_name));
- ASSERT_EQ(result::SUCCESS, zone2.code);
- // And the second call should return false since
- // it already exists
- ASSERT_FALSE(this->client_->createZone(new_name));
- }
+ // Adding a new zone; it should work and return true
+ ASSERT_TRUE(this->client_->createZone(new_name));
+ const DataSourceClient::FindResult
+ zone2(this->client_->findZone(new_name));
+ ASSERT_EQ(result::SUCCESS, zone2.code);
+ // And the second call should return false since
+ // it already exists
+ this->allowMoreTransaction(true);
+ ASSERT_FALSE(this->client_->createZone(new_name));
}
TYPED_TEST(DatabaseClientTest, createZoneRollbackOnLocked) {
- // skip test for mock
- if (this->is_mock_) {
- return;
- }
-
const Name new_name("example.com");
isc::datasrc::ZoneUpdaterPtr updater =
this->client_->getUpdater(this->zname_, true);
+ this->allowMoreTransaction(false);
ASSERT_THROW(this->client_->createZone(new_name), DataSourceError);
// createZone started a transaction as well, but since it failed,
// it should have been rolled back. Roll back the other one as
// well, and the next attempt should succeed
updater.reset();
+ this->allowMoreTransaction(true);
ASSERT_TRUE(this->client_->createZone(new_name));
}
TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
- // skip test for mock
- if (this->is_mock_) {
- return;
- }
-
const Name new_name("example.com");
ASSERT_FALSE(this->client_->createZone(this->zname_));
- // createZone started a transaction, but since it failed,
- // it should have been rolled back, and the next attempt should succeed
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
ASSERT_TRUE(this->client_->createZone(new_name));
}
+TYPED_TEST(DatabaseClientTest, deleteZone) {
+ // Check the zone currently exists.
+ EXPECT_EQ(result::SUCCESS, this->client_->findZone(this->zname_).code);
+
+ // Deleting an existing zone; it should work and return true (previously
+ // existed and is now deleted)
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+
+ // Now it's not found by findZone
+ EXPECT_EQ(result::NOTFOUND, this->client_->findZone(this->zname_).code);
+
+ // And the second call should return false since it doesn't exist any more
+ this->allowMoreTransaction(true);
+ EXPECT_FALSE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnLocked) {
+ isc::datasrc::ZoneUpdaterPtr updater =
+ this->client_->getUpdater(this->zname_, true);
+
+ // updater locks the DB so deleteZone() will fail.
+ this->allowMoreTransaction(false);
+ EXPECT_THROW(this->client_->deleteZone(this->zname_), DataSourceError);
+
+ // deleteZone started a transaction as well, but since it failed,
+ // it should have been rolled back. Roll back the other one as
+ // well, and the next attempt should succeed
+ updater.reset();
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnNotFind) {
+ // attempt of deleting non-existent zone. result in false
+ const Name new_name("example.com");
+ EXPECT_FALSE(this->client_->deleteZone(new_name));
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
TYPED_TEST_CASE(RRsetCollectionTest, TestAccessorTypes);
// This test fixture is templated so that we can share (most of) the test
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index 58c6cb1..a45c2bd 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -71,20 +71,20 @@ struct TestRdata {
// unusual and corner cases).
const TestRdata test_rdata_list[] = {
{"IN", "A", "192.0.2.1", 0},
- {"IN", "NS", "ns.example.com", 0},
- {"IN", "CNAME", "cname.example.com", 0},
- {"IN", "SOA", "ns.example.com root.example.com 0 0 0 0 0", 0},
- {"IN", "PTR", "reverse.example.com", 0},
+ {"IN", "NS", "ns.example.com.", 0},
+ {"IN", "CNAME", "cname.example.com.", 0},
+ {"IN", "SOA", "ns.example.com. root.example.com. 0 0 0 0 0", 0},
+ {"IN", "PTR", "reverse.example.com.", 0},
{"IN", "HINFO", "\"cpu-info\" \"OS-info\"", 1},
- {"IN", "MINFO", "root.example.com mbox.example.com", 0},
- {"IN", "MX", "10 mx.example.com", 0},
+ {"IN", "MINFO", "root.example.com. mbox.example.com.", 0},
+ {"IN", "MX", "10 mx.example.com.", 0},
{"IN", "TXT", "\"test1\" \"test 2\"", 1},
- {"IN", "RP", "root.example.com. rp-text.example.com", 0},
- {"IN", "AFSDB", "1 afsdb.example.com", 0},
+ {"IN", "RP", "root.example.com. rp-text.example.com.", 0},
+ {"IN", "AFSDB", "1 afsdb.example.com.", 0},
{"IN", "AAAA", "2001:db8::1", 0},
- {"IN", "SRV", "1 0 10 target.example.com", 0},
- {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com", 1},
- {"IN", "DNAME", "dname.example.com", 0},
+ {"IN", "SRV", "1 0 10 target.example.com.", 0},
+ {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com.", 1},
+ {"IN", "DNAME", "dname.example.com.", 0},
{"IN", "DS", "12892 5 2 5F0EB5C777586DE18DA6B5", 1},
{"IN", "SSHFP", "1 1 dd465c09cfa51fb45020cc83316fff", 1},
// We handle RRSIG separately, so it's excluded from the list
@@ -98,7 +98,7 @@ const TestRdata test_rdata_list[] = {
{"IN", "TYPE65000", "\\# 3 010203", 1}, // some "custom" type
{"IN", "TYPE65535", "\\# 0", 1}, // max RR type, 0-length RDATA
{"CH", "A", "\\# 2 0102", 1}, // A RR for non-IN class; varlen data
- {"CH", "NS", "ns.example.com", 0}, // class CH, generic data
+ {"CH", "NS", "ns.example.com.", 0}, // class CH, generic data
{"CH", "TXT", "BIND10", 1}, // ditto
{"HS", "A", "\\# 5 0102030405", 1}, // A RR for non-IN class; varlen data
{NULL, NULL, NULL, 0}
@@ -409,6 +409,7 @@ public:
reader.nextSig();
reader.rewind();
// Do the actual rendering
+ // cppcheck-suppress unreadVariable
current = &renderer;
reader.iterate();
renderer.writeName(dummyName2());
@@ -484,6 +485,8 @@ public:
current = NULL;
reader.iterateAllSigs();
// Now return the renderer and render the rest of the data
+ // cppcheck-suppress redundantAssignment
+ // cppcheck-suppress unreadVariable
current = &renderer;
reader.iterate();
// Now, this should not break anything and should be valid, but should
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 8dff6d5..c263304 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -20,6 +20,8 @@
#include <dns/rrclass.h>
+#include <exceptions/exceptions.h>
+
#include <sqlite3.h>
#include <gtest/gtest.h>
@@ -1615,4 +1617,50 @@ TEST_F(SQLite3Update, addZoneWhileLocked) {
EXPECT_FALSE(accessor->getZone(new_zone).first);
}
+//
+// Tests for deleteZone() follow.
+//
+TEST_F(SQLite3Update, deleteZone) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Calling deleteZone without transaction should fail
+ EXPECT_THROW(accessor->deleteZone(zone_info.first), isc::InvalidOperation);
+
+ // Delete the zone. Then confirm it, both before and after commit.
+ accessor->startTransaction();
+ accessor->deleteZone(zone_info.second);
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+ accessor->commit();
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+
+ // Records are not deleted.
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_TRUE(accessor->getRecords("example.com.", zone_id, false)
+ ->getNext(data));
+}
+
+TEST_F(SQLite3Update, deleteZoneWhileLocked) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Adding another (not commit yet), it should lock the db
+ const std::string new_zone = "new.example.com.";
+ accessor->startTransaction();
+ zone_id = accessor->addZone(new_zone);
+
+ // deleteZone should throw an exception that it is locked
+ another_accessor->startTransaction();
+ EXPECT_THROW(another_accessor->deleteZone(zone_id), DataSourceError);
+ // Commit should do nothing, but not fail
+ another_accessor->commit();
+
+ accessor->rollback();
+
+ // The zone should still exist.
+ EXPECT_TRUE(accessor->getZone("example.com.").first);
+}
+
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 6844712..85b167e 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -81,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
}
DataSourceClientPtr
-createSQLite3Client(RRClass zclass, const Name& zname) {
+createSQLite3Client(RRClass zclass, const Name& zname, stringstream& ss) {
// We always begin with an empty template SQLite3 DB file and install
// the zone data from the zone file to ensure both cases have the
// same test data.
@@ -93,7 +93,6 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
// Note that neither updater nor SQLite3 accessor checks this condition,
// so this should succeed.
ZoneUpdaterPtr updater = client->getUpdater(zname, false);
- stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
masterLoad(ss, Name::ROOT_NAME(), zclass,
boost::bind(addRRset, updater, _1));
updater->commit();
@@ -101,6 +100,12 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
return (client);
}
+DataSourceClientPtr
+createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
+ stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+ return (createSQLite3Client(zclass, zname, ss));
+}
+
// The test class. Its parameterized so we can share the test scnearios
// for any concrete data source implementaitons.
class ZoneFinderContextTest :
@@ -134,7 +139,7 @@ protected:
// We test the in-memory and SQLite3 data source implementations.
INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
::testing::Values(createInMemoryClient,
- createSQLite3Client));
+ createSQLite3ClientWithNS));
TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
@@ -430,4 +435,25 @@ TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
result_sets_.begin(), result_sets_.end());
}
+TEST(ZoneFinderContextSQLite3Test, escapedText) {
+ // This test checks that TXTLike data, when written to a database,
+ // is escaped correctly before stored in the database. The actual
+ // escaping is done in the toText() method of TXTLike objects, but
+ // we check anyway if this also carries over to the ZoneUpdater.
+ RRClass zclass(RRClass::IN());
+ Name zname("example.org");
+ stringstream ss("escaped.example.org. 3600 IN TXT Hello~World\\;\\\"");
+ DataSourceClientPtr client = createSQLite3Client(zclass, zname, ss);
+ ZoneFinderPtr finder = client->findZone(zname).zone_finder;
+
+ // If there is no escaping, the following will throw an exception
+ // when it tries to construct a TXT RRset using the data from the
+ // database.
+ ZoneFinderContextPtr ctx = finder->find(Name("escaped.example.org"),
+ RRType::TXT());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ EXPECT_EQ("escaped.example.org. 3600 IN TXT \"Hello~World\\;\\\"\"\n",
+ ctx->rrset->toText());
+}
+
}
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 571f11d..822c4e3 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,23 +14,24 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libb10-dhcp++.la
libb10_dhcp___la_SOURCES =
+libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
libb10_dhcp___la_SOURCES += duid.cc duid.h
+libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
-libb10_dhcp___la_SOURCES += option.cc option.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_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
-libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
-libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
+libb10_dhcp___la_SOURCES += option.cc option.h
+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 += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcp___la_SOURCES += std_option_defs.h
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 91efe94..f1c8866 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -95,6 +95,16 @@ const std::vector<uint8_t> ClientId::getClientId() const {
return (duid_);
}
+// Returns the Client ID in text form
+std::string ClientId::toText() const {
+
+ // As DUID is a private base class of ClientId, we can't access
+ // its public toText() method through inheritance: instead we
+ // need the interface of a ClientId::toText() that calls the
+ // equivalent method in the base class.
+ return (DUID::toText());
+}
+
// Compares two client-ids
bool ClientId::operator==(const ClientId& other) const {
return (this->duid_ == other.duid_);
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 60b9706..688885b 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -86,7 +86,7 @@ typedef boost::shared_ptr<DUID> DuidPtr;
///
/// This class is intended to be a generic IPv4 client identifier. It can hold
/// a client-id
-class ClientId : DUID {
+class ClientId : public DUID {
public:
/// @brief Maximum size of a client ID
///
@@ -107,6 +107,9 @@ public:
/// @brief Returns reference to the client-id data
const std::vector<uint8_t> getClientId() const;
+ /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
+ std::string toText() const;
+
/// @brief Compares two client-ids for equality
bool operator==(const ClientId& other) const;
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
new file mode 100644
index 0000000..d19f2ad
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+HWAddr::HWAddr()
+ :htype_(HTYPE_ETHER) {
+}
+
+HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
+ :hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+}
+
+HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
+ :hwaddr_(hwaddr), htype_(htype) {
+}
+
+std::string HWAddr::toText() const {
+ std::stringstream tmp;
+ tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
+ it != hwaddr_.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+bool HWAddr::operator==(const HWAddr& other) const {
+ return ((this->htype_ == other.htype_) &&
+ (this->hwaddr_ == other.hwaddr_));
+}
+
+bool HWAddr::operator!=(const HWAddr& other) const {
+ return !(*this == other);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
new file mode 100644
index 0000000..93b06a1
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <vector>
+#include <stdint.h>
+#include <stddef.h>
+#include <dhcp/dhcp4.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware type that represents information from DHCPv4 packet
+struct HWAddr {
+public:
+
+ /// @brief default constructor
+ HWAddr();
+
+ /// @brief constructor, based on C-style pointer and length
+ /// @param hwaddr pointer to hardware address
+ /// @param len length of the address pointed by hwaddr
+ /// @param htype hardware type
+ HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype);
+
+ /// @brief constructor, based on C++ vector<uint8_t>
+ /// @param hwaddr const reference to hardware address
+ /// @param htype hardware type
+ HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype);
+
+ // Vector that keeps the actual hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ // Hardware type
+ uint8_t htype_;
+
+ /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
+ std::string toText() const;
+
+ /// @brief Compares two hardware addresses for equality
+ bool operator==(const HWAddr& other) const;
+
+ /// @brief Compares two hardware addresses for inequality
+ bool operator!=(const HWAddr& other) const;
+};
+
+/// @brief Shared pointer to a hardware address structure
+typedef boost::shared_ptr<HWAddr> HWAddrPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index b19ed21..a3921cc 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -74,6 +74,34 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
return (OptionDefinitionPtr());
}
+bool
+LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
+ if (u == Option::V6) {
+ if (code < 79 &&
+ code != 10 &&
+ code != 35) {
+ return (true);
+ }
+
+ } else if (u == Option::V4) {
+ if (!(code == 84 ||
+ code == 96 ||
+ (code > 101 && code < 112) ||
+ code == 115 ||
+ code == 126 ||
+ code == 127 ||
+ (code > 146 && code < 150) ||
+ (code > 177 && code < 208) ||
+ (code > 213 && code < 220) ||
+ (code > 221 && code < 224))) {
+ return (true);
+ }
+
+ }
+
+ return (false);
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c325aa5..bc47405 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -55,6 +55,21 @@ public:
static OptionDefinitionPtr getOptionDef(const Option::Universe u,
const uint16_t code);
+ /// @brief Check if the specified option is a standard option.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return true if the specified option is a standard option.
+ /// @todo We arleady create option definitions for the subset if
+ /// standard options. We are aiming that this function checks
+ /// the presence of the standard option definition and if it finds
+ /// it, then the true value is returned. However, at this point
+ /// this is not doable because some of the definitions (for less
+ /// important options) are not created yet.
+ static bool isStandardOption(const Option::Universe u,
+ const uint16_t code);
+
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index e1d7cb6..d2b5aae 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -22,6 +22,9 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace isc::util;
@@ -176,10 +179,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
if (values.empty()) {
isc_throw(InvalidOptionValue, "no option value specified");
}
- writeToBuffer(values[0], type_, buf);
+ writeToBuffer(util::str::trim(values[0]), type_, buf);
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
for (size_t i = 0; i < values.size(); ++i) {
- writeToBuffer(values[i], type_, buf);
+ writeToBuffer(util::str::trim(values[i]), type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
@@ -189,7 +192,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
- writeToBuffer(values[i], records[i], buf);
+ writeToBuffer(util::str::trim(values[i]),
+ records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
@@ -205,16 +209,29 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
void
OptionDefinition::validate() const {
+
+ using namespace boost::algorithm;
+
std::ostringstream err_str;
- if (name_.empty()) {
- // Option name must not be empty.
- err_str << "option name must not be empty.";
- } else if (name_.find(" ") != string::npos) {
- // Option name must not contain spaces.
- err_str << "option name must not contain spaces.";
+
+ // Allowed characters in the option name are: lower or
+ // upper case letters, digits, underscores and hyphens.
+ // Empty option spaces are not allowed.
+ if (!all(name_, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) ||
+ name_.empty() ||
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option name.
+ all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+ all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+ err_str << "invalid option name '" << name_ << "'";
+
} else if (type_ >= OPT_UNKNOWN_TYPE) {
// Option definition must be of a known type.
err_str << "option type value " << type_ << " is out of range.";
+
} else if (array_type_) {
if (type_ == OPT_STRING_TYPE) {
// Array of strings is not allowed because there is no way
@@ -223,9 +240,12 @@ OptionDefinition::validate() const {
err_str << "array of strings is not a valid option definition.";
} else if (type_ == OPT_BINARY_TYPE) {
err_str << "array of binary values is not a valid option definition.";
+
} else if (type_ == OPT_EMPTY_TYPE) {
err_str << "array of empty value is not a valid option definition.";
+
}
+
} else if (type_ == OPT_RECORD_TYPE) {
// At least two data fields should be added to the record. Otherwise
// non-record option definition could be used.
@@ -233,6 +253,7 @@ OptionDefinition::validate() const {
err_str << "invalid number of data fields: " << getRecordFields().size()
<< " specified for the option of type 'record'. Expected at"
<< " least 2 fields.";
+
} else {
// If the number of fields is valid we have to check if their order
// is valid too. We check that string or binary data fields are not
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 9f6bef2..efcaba0 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.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
@@ -42,6 +42,14 @@ public:
isc::Exception(file, line, what) { };
};
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+ DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Forward declaration to OptionDefinition.
class OptionDefinition;
@@ -361,7 +369,7 @@ public:
/// @brief Factory function to create option with array of integer values.
///
- /// @param universe (V4 or V6).
+ /// @param u universe (V4 or V6).
/// @param type option type.
/// @param begin iterator pointing to the beginning of the buffer.
/// @param end iterator pointing to the end of the buffer.
@@ -492,6 +500,9 @@ typedef boost::multi_index_container<
>
> OptionDefContainer;
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
/// Type of the index #1 - option type.
typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
/// Pair of iterators to represent the range of options definitions
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 15c8f4e..d3b22de 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -40,8 +40,7 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(DHCPTypeToBootpType(msg_type)),
- htype_(HTYPE_ETHER),
- hlen_(0),
+ hwaddr_(new HWAddr()),
hops_(0),
transid_(transid),
secs_(0),
@@ -50,12 +49,12 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(DHCPV4_PKT_HDR_LEN),
- msg_type_(msg_type)
+ bufferOut_(DHCPV4_PKT_HDR_LEN)
{
- memset(chaddr_, 0, MAX_CHADDR_LEN);
memset(sname_, 0, MAX_SNAME_LEN);
memset(file_, 0, MAX_FILE_LEN);
+
+ setType(msg_type);
}
Pkt4::Pkt4(const uint8_t* data, size_t len)
@@ -66,6 +65,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(BOOTREQUEST),
+ hwaddr_(new HWAddr()),
transid_(0),
secs_(0),
flags_(0),
@@ -73,8 +73,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(0), // not used, this is RX packet
- msg_type_(DHCPDISCOVER)
+ bufferOut_(0) // not used, this is RX packet
{
if (len < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
@@ -105,9 +104,15 @@ Pkt4::len() {
bool
Pkt4::pack() {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
+ }
+
+ size_t hw_len = hwaddr_->hwaddr_.size();
+
bufferOut_.writeUint8(op_);
- bufferOut_.writeUint8(htype_);
- bufferOut_.writeUint8(hlen_);
+ bufferOut_.writeUint8(hwaddr_->htype_);
+ bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
bufferOut_.writeUint8(hops_);
bufferOut_.writeUint32(transid_);
bufferOut_.writeUint16(secs_);
@@ -116,7 +121,23 @@ Pkt4::pack() {
bufferOut_.writeUint32(yiaddr_);
bufferOut_.writeUint32(siaddr_);
bufferOut_.writeUint32(giaddr_);
- bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+
+ if (hw_len <= MAX_CHADDR_LEN) {
+ // write up to 16 bytes of the hardware address (CHADDR field is 16
+ // bytes long in DHCPv4 message).
+ bufferOut_.writeData(&hwaddr_->hwaddr_[0],
+ (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
+ hw_len = MAX_CHADDR_LEN - hw_len;
+ } else {
+ hw_len = MAX_CHADDR_LEN;
+ }
+
+ // write (len) bytes of padding
+ vector<uint8_t> zeros(hw_len, 0);
+ bufferOut_.writeData(&zeros[0], hw_len);
+ // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
bufferOut_.writeData(sname_, MAX_SNAME_LEN);
bufferOut_.writeData(file_, MAX_FILE_LEN);
@@ -145,8 +166,8 @@ Pkt4::unpack() {
}
op_ = bufferIn.readUint8();
- htype_ = bufferIn.readUint8();
- hlen_ = bufferIn.readUint8();
+ uint8_t htype = bufferIn.readUint8();
+ uint8_t hlen = bufferIn.readUint8();
hops_ = bufferIn.readUint8();
transid_ = bufferIn.readUint32();
secs_ = bufferIn.readUint16();
@@ -155,10 +176,16 @@ Pkt4::unpack() {
yiaddr_ = IOAddress(bufferIn.readUint32());
siaddr_ = IOAddress(bufferIn.readUint32());
giaddr_ = IOAddress(bufferIn.readUint32());
- bufferIn.readData(chaddr_, MAX_CHADDR_LEN);
+
+ vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
+ bufferIn.readVector(hw_addr, MAX_CHADDR_LEN);
bufferIn.readData(sname_, MAX_SNAME_LEN);
bufferIn.readData(file_, MAX_FILE_LEN);
+ hw_addr.resize(hlen);
+
+ hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
+
if (bufferIn.getLength() == bufferIn.getPosition()) {
// this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
// particular, it does not have magic cookie, a 4 byte sequence that
@@ -189,21 +216,44 @@ Pkt4::unpack() {
}
void Pkt4::check() {
+ uint8_t msg_type = getType();
+ if (msg_type > DHCPLEASEACTIVE) {
+ isc_throw(BadValue, "Invalid DHCP message type received: "
+ << msg_type);
+ }
+}
+
+uint8_t Pkt4::getType() const {
+ OptionPtr generic = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (!generic) {
+ isc_throw(Unexpected, "Missing DHCP Message Type option");
+ }
+
+ // Check if Message Type is specified as OptionInt<uint8_t>
boost::shared_ptr<OptionInt<uint8_t> > typeOpt =
- boost::dynamic_pointer_cast<OptionInt<uint8_t> >(getOption(DHO_DHCP_MESSAGE_TYPE));
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
if (typeOpt) {
- uint8_t msg_type = typeOpt->getValue();
- if (msg_type > DHCPLEASEACTIVE) {
- isc_throw(BadValue, "Invalid DHCP message type received: "
- << msg_type);
- }
- msg_type_ = msg_type;
+ return (typeOpt->getValue());
+ }
+ // Try to use it as generic option
+ return (generic->getUint8());
+}
+
+void Pkt4::setType(uint8_t dhcp_type) {
+ OptionPtr opt = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (opt) {
+ // There is message type option already, update it
+ opt->setUint8(dhcp_type);
} else {
- isc_throw(Unexpected, "Missing DHCP Message Type option");
+ // There is no message type option yet, add it
+ std::vector<uint8_t> tmp(1, dhcp_type);
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
+ addOption(opt);
}
}
+
void Pkt4::repack() {
bufferOut_.writeData(&data_[0], data_.size());
}
@@ -213,7 +263,7 @@ Pkt4::toText() {
stringstream tmp;
tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
<< " remoteAddr=" << remote_addr_.toText()
- << ":" << remote_port_ << ", msgtype=" << int(msg_type_)
+ << ":" << remote_port_ << ", msgtype=" << getType()
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
@@ -239,10 +289,15 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
isc_throw(OutOfRange, "Invalid HW Address specified");
}
- htype_ = hType;
- hlen_ = hlen;
- std::copy(&mac_addr[0], &mac_addr[hlen], &chaddr_[0]);
- std::fill(&chaddr_[hlen], &chaddr_[MAX_CHADDR_LEN], 0);
+ hwaddr_.reset(new HWAddr(mac_addr, hType));
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+ }
+ hwaddr_ = addr;
}
void
@@ -302,6 +357,23 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
}
}
+uint8_t
+Pkt4::getHtype() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ return (hwaddr_->htype_);
+}
+
+uint8_t
+Pkt4::getHlen() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ uint8_t len = hwaddr_->hwaddr_.size();
+ return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
+}
+
void
Pkt4::addOption(boost::shared_ptr<Option> opt) {
// Check for uniqueness (DHCPv4 options must be unique)
@@ -313,7 +385,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
}
boost::shared_ptr<isc::dhcp::Option>
-Pkt4::getOption(uint8_t type) {
+Pkt4::getOption(uint8_t type) const {
Option::OptionCollection::const_iterator x = options_.find(type);
if (x != options_.end()) {
return (*x).second;
@@ -321,6 +393,16 @@ Pkt4::getOption(uint8_t type) {
return boost::shared_ptr<isc::dhcp::Option>(); // NULL
}
+bool
+Pkt4::delOption(uint8_t type) {
+ isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ }
+ return (false); // can't find option to be deleted
+}
+
void
Pkt4::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 5139458..664686b 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <util/buffer.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/shared_ptr.hpp>
@@ -217,16 +218,15 @@ public:
/// @return transaction-id
uint32_t getTransid() const { return (transid_); };
- /// @brief Returns message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @return message type
- uint8_t
- getType() const { return (msg_type_); }
+ uint8_t getType() const;
- /// @brief Sets message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @param type message type to be set
- void setType(uint8_t type) { msg_type_=type; };
+ void setType(uint8_t type);
/// @brief Returns sname field
///
@@ -273,27 +273,28 @@ public:
void setHWAddr(uint8_t hType, uint8_t hlen,
const std::vector<uint8_t>& mac_addr);
+ /// @brief Sets hardware address
+ ///
+ /// Sets hardware address, based on existing HWAddr structure
+ /// @param addr already filled in HWAddr structure
+ /// @throw BadValue if addr is null
+ void setHWAddr(const HWAddrPtr& addr);
+
/// Returns htype field
///
/// @return hardware type
uint8_t
- getHtype() const { return (htype_); };
+ getHtype() const;
/// Returns hlen field
///
/// @return hardware address length
uint8_t
- getHlen() const { return (hlen_); };
-
- /// @brief Returns chaddr field.
- ///
- /// Note: This is 16 bytes long field. It doesn't have to be
- /// null-terminated. Do no use strlen() or similar on it.
- ///
- /// @return pointer to hardware address
- const uint8_t*
- getChaddr() const { return (chaddr_); };
+ getHlen() const;
+ /// @brief returns hardware address information
+ /// @return hardware address structure
+ HWAddrPtr getHWAddr() const { return (hwaddr_); }
/// @brief Returns reference to output buffer.
///
@@ -321,7 +322,12 @@ public:
/// @return returns option of requested type (or NULL)
/// if no such option is present
boost::shared_ptr<Option>
- getOption(uint8_t opt_type);
+ getOption(uint8_t opt_type) const;
+
+ /// @brief Deletes specified option
+ /// @param type option type to be deleted
+ /// @return true if anything was deleted, false otherwise
+ bool delOption(uint8_t type);
/// @brief Returns interface name.
///
@@ -454,11 +460,11 @@ protected:
/// type is kept in message type option).
uint8_t op_;
- /// link-layer address type
- uint8_t htype_;
-
- /// link-layer address length
- uint8_t hlen_;
+ /// @brief link-layer address and hardware information
+ /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the
+ /// hardware address, up to 16) and chaddr (hardware address field,
+ /// 16 bytes)
+ HWAddrPtr hwaddr_;
/// Number of relay agents traversed
uint8_t hops_;
@@ -484,9 +490,6 @@ protected:
/// giaddr field (32 bits): Gateway IP address
isc::asiolink::IOAddress giaddr_;
- /// Hardware address field (16 bytes)
- uint8_t chaddr_[MAX_CHADDR_LEN];
-
/// sname field (64 bytes)
uint8_t sname_[MAX_SNAME_LEN];
@@ -518,16 +521,11 @@ protected:
/// data format change etc.
std::vector<uint8_t> data_;
- /// message type (e.g. 1=DHCPDISCOVER)
- /// @todo this will eventually be replaced with DHCP Message Type
- /// option (option 53)
- uint8_t msg_type_;
-
/// collection of options present in this message
///
/// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+ /// @ref perfdhcp::PerfPkt4. The impact on derived classes'
/// behavior must be taken into consideration before making
/// changes to this member such as access scope restriction or
/// data format change etc.
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 4d177d1..4833fb5 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -27,6 +27,7 @@ if HAVE_GTEST
TESTS += libdhcp++_unittests
libdhcp___unittests_SOURCES = run_unittests.cc
+libdhcp___unittests_SOURCES += hwaddr_unittest.cc
libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 9db4c35..de20e51 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -108,6 +108,14 @@ TEST(DuidTest, getType) {
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
@@ -174,8 +182,8 @@ TEST(ClientIdTest, operators) {
TEST(ClientIdTest, toText) {
uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
- DUID duid(data1, sizeof(data1));
- EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+ ClientId clientid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..144d62d
--- /dev/null
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(HWAddrTest, constructor) {
+
+ const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ const uint8_t htype = HTYPE_ETHER;
+
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
+ scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
+ scoped_ptr<HWAddr> hwaddr3(new HWAddr());
+
+ EXPECT_TRUE(data2 == hwaddr1->hwaddr_);
+ EXPECT_EQ(htype, hwaddr1->htype_);
+
+ EXPECT_TRUE(data2 == hwaddr2->hwaddr_);
+ EXPECT_EQ(htype, hwaddr2->htype_);
+
+ EXPECT_EQ(0, hwaddr3->hwaddr_.size());
+ EXPECT_EQ(htype, hwaddr3->htype_);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(HWAddrTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ uint8_t htype1 = HTYPE_ETHER;
+ uint8_t htype2 = HTYPE_FDDI;
+
+ scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1));
+ scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1));
+ scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1));
+ scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1));
+
+ // MAC address the same as data1 and data4, but different hardware type
+ scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2));
+
+ EXPECT_TRUE(*hw1 == *hw4);
+ EXPECT_FALSE(*hw1 == *hw2);
+ EXPECT_FALSE(*hw1 == *hw3);
+
+ EXPECT_FALSE(*hw1 != *hw4);
+ EXPECT_TRUE(*hw1 != *hw2);
+ EXPECT_TRUE(*hw1 != *hw3);
+
+ EXPECT_FALSE(*hw1 == *hw5);
+ EXPECT_FALSE(*hw4 == *hw5);
+
+ EXPECT_TRUE(*hw1 != *hw5);
+ EXPECT_TRUE(*hw4 != *hw5);
+}
+
+// Checks that toText() method produces appropriate text representation
+TEST(HWAddrTest, toText) {
+ uint8_t data[] = {0, 1, 2, 3, 4, 5};
+ uint8_t htype = 15;
+
+ HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+ EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
+
+}
+
+TEST(HWAddrTest, stringConversion) {
+
+ // Check that an empty vector returns an appropriate string
+ HWAddr hwaddr;
+ std::string result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 "), result);
+
+ // ... that a single-byte string is OK
+ hwaddr.hwaddr_.push_back(0xc3);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3"), result);
+
+ // ... and that a multi-byte string works
+ hwaddr.hwaddr_.push_back(0x7);
+ hwaddr.hwaddr_.push_back(0xa2);
+ hwaddr.hwaddr_.push_back(0xe8);
+ hwaddr.hwaddr_.push_back(0x42);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 11cbf34..d72ef9e 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -702,11 +702,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
sendPkt->setYiaddr(IOAddress("192.0.2.3"));
sendPkt->setGiaddr(IOAddress("192.0.2.4"));
- // unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present
- boost::shared_ptr<Option> msgType(new Option(Option::V4,
- static_cast<uint16_t>(DHO_DHCP_MESSAGE_TYPE)));
- msgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
- sendPkt->addOption(msgType);
+ // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+ // Workarounds (creating DHCP Message Type Option by hand) are no longer
+ // needed as setDhcpType() is called in constructor.
uint8_t sname[] = "That's just a string that will act as SNAME";
sendPkt->setSname(sname, strlen((const char*)sname));
@@ -744,7 +742,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
- EXPECT_EQ(sendPkt->getType(), rcvPkt->getType());
EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index e44ef58..a59da12 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -453,6 +453,66 @@ TEST_F(LibDhcpTest, unpackOptions4) {
EXPECT_TRUE(x == options.end()); // option 2 not found
}
+TEST_F(LibDhcpTest, isStandardOption4) {
+ // Get all option codes that are not occupied by standard options.
+ const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 115, 126, 127, 147, 148, 149,
+ 178, 179, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 190, 191, 192, 193, 194, 195,
+ 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 205, 206, 207, 214, 215, 216, 217, 218, 219,
+ 222, 223 };
+ const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
+
+ // Try all possible option codes.
+ for (size_t i = 0; i < 256; ++i) {
+ // Some ranges of option codes are unassigned and thus the isStandardOption
+ // should return false for them.
+ bool check_unassigned = false;
+ // Check the array of unassigned options to find out whether option code
+ // is assigned to standard option or unassigned.
+ for (size_t j = 0; j < unassigned_num; ++j) {
+ // If option code is found within the array of unassigned options
+ // we the isStandardOption function should return false.
+ if (unassigned_codes[j] == i) {
+ check_unassigned = true;
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
+ unassigned_codes[j]))
+ << "Test failed for option code " << unassigned_codes[j];
+ break;
+ }
+ }
+ // If the option code belongs to the standard option then the
+ // isStandardOption should return true.
+ if (!check_unassigned) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
+ << "Test failed for the option code " << i;
+ }
+ }
+}
+
+TEST_F(LibDhcpTest, isStandardOption6) {
+ // All option codes in the range from 0 to 78 (except 10 and 35)
+ // identify the standard options.
+ for (uint16_t code = 0; code < 79; ++code) {
+ if (code != 10 && code != 35) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+ }
+
+ // Check the option codes 10 and 35. They are unassigned.
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
+
+ // Check a range of option codes above 78. Those are option codes
+ // identifying non-standard options.
+ for (uint16_t code = 79; code < 512; ++code) {
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+}
+
TEST_F(LibDhcpTest, stdOptionDefs4) {
// Create a buffer that holds dummy option data.
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index deb61b5..310c7bf 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -164,29 +164,55 @@ TEST_F(OptionDefinitionTest, validate) {
EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
// Option name must not contain spaces.
- OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
+ OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+ // Option name may contain lower case letters.
+ OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def7.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def8("option_123", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def8.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def9.validate());
+
+ // Using hyphen or undescore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
// Having array of strings does not make sense because there is no way
// to determine string's length.
- OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
- EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+ EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
// It does not make sense to have string field within the record before
// other fields because there is no way to determine the length of this
// string and thus there is no way to determine where the other field
// begins.
- OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def8.addRecordField("string");
- opt_def8.addRecordField("uint16");
- EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def15("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def15.addRecordField("string");
+ opt_def15.addRecordField("uint16");
+ EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
// ... but it is ok if the string value is the last one.
- OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def9.addRecordField("uint8");
- opt_def9.addRecordField("string");
+ OptionDefinition opt_def16("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def16.addRecordField("uint8");
+ opt_def16.addRecordField("string");
}
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index bd848ff..49588e1 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -69,8 +69,9 @@ TEST(Pkt4Test, constructor) {
pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
);
- // DHCPv4 packet must be at least 236 bytes long
- EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // DHCPv4 packet must be at least 236 bytes long, with Message Type
+ // Option taking extra 3 bytes it is 239
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
EXPECT_NO_THROW(
@@ -219,8 +220,10 @@ TEST(Pkt4Test, fixedFields) {
EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText());
EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText());
- // chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), 16));
+ // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+ // long and its length depends on hlen value (it is up to 16 bytes now).
+ ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
@@ -237,7 +240,9 @@ TEST(Pkt4Test, fixedFieldsPack) {
pkt->pack();
);
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // Minimum packet size is 236 bytes + 3 bytes of mandatory
+ // DHCP Message Type Option
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
// redundant but MUCH easier for debug in gdb
const uint8_t* exp = &expectedFormat[0];
@@ -282,7 +287,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText());
// chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), Pkt4::MAX_CHADDR_LEN));
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -321,7 +326,7 @@ TEST(Pkt4Test, hwAddr) {
pkt->setHWAddr(255-macLen*10, // just weird htype
macLen,
mac);
- EXPECT_EQ(0, memcmp(expectedChaddr, pkt->getChaddr(),
+ EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
Pkt4::MAX_CHADDR_LEN));
EXPECT_NO_THROW(
@@ -469,7 +474,7 @@ TEST(Pkt4Test, file) {
static uint8_t v4Opts[] = {
12, 3, 0, 1, 2, // Hostname
14, 3, 10, 11, 12, // Merit Dump File
- 53, 1, 1, // Message Type (required to not throw exception during unpack)
+ 53, 1, 2, // Message Type (required to not throw exception during unpack)
60, 3, 20, 21, 22, // Class Id
128, 3, 30, 31, 32, // Vendor specific
254, 3, 40, 41, 42, // Reserved
@@ -487,18 +492,15 @@ TEST(Pkt4Test, options) {
boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
- boost::shared_ptr<Option> optMsgType(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE));
boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
- optMsgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
pkt->addOption(opt1);
pkt->addOption(opt2);
pkt->addOption(opt3);
pkt->addOption(opt4);
pkt->addOption(opt5);
- pkt->addOption(optMsgType);
EXPECT_TRUE(pkt->getOption(12));
EXPECT_TRUE(pkt->getOption(60));
@@ -530,6 +532,12 @@ TEST(Pkt4Test, options) {
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
+ // delOption() checks
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+ EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
EXPECT_NO_THROW(
delete pkt;
);
@@ -643,6 +651,23 @@ TEST(Pkt4Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
+TEST(Pkt4Test, hwaddr) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+
+ HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ // setting NULL hardware address is not allowed
+ EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+ pkt->setHWAddr(hwaddr);
+
+ EXPECT_EQ(hw_type, pkt->getHtype());
+
+ EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+ EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b44fa7d..cc5daec 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -8,28 +8,48 @@ endif
AM_CXXFLAGS = $(B10_CXXFLAGS)
+# Define rule to build logging source files from message file
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+# Tell Automake that the dhcpsrv_messages.{cc,h} source files are created in the
+# build process, so it must create these before doing anything else. Although
+# they are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created. As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
# Some versions of GCC warn about some versions of Boost regarding
# missing initializer for members in its posix_time.
# https://svn.boost.org/trac/boost/ticket/3477
# But older GCC compilers don't have the flag.
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-CLEANFILES = *.gcno *.gcda
+# Make sure the generated files are deleted in a "clean" operation
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
+libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
+libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
+libb10_dhcpsrv_la_SOURCES += utils.h
+
+nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
@@ -47,6 +67,9 @@ if USE_CLANGPP
libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
endif
+# The message file should be in the distribution
+EXTRA_DIST = dhcpsrv_messages.mes
+
# Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
+EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
diff --git a/src/lib/dhcpsrv/addr_utilities.cc b/src/lib/dhcpsrv/addr_utilities.cc
index 98fbeb9..d32c209 100644
--- a/src/lib/dhcpsrv/addr_utilities.cc
+++ b/src/lib/dhcpsrv/addr_utilities.cc
@@ -195,5 +195,15 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
}
}
+isc::asiolink::IOAddress getNetmask4(uint8_t len) {
+ if (len > 32) {
+ isc_throw(BadValue, "Invalid netmask size " << len << ", allowed range "
+ "is 0..32");
+ }
+ uint32_t x = ~bitMask4[len];
+
+ return (IOAddress(x));
+}
+
};
};
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index 784aeab..6aef574 100644
--- a/src/lib/dhcpsrv/addr_utilities.h
+++ b/src/lib/dhcpsrv/addr_utilities.h
@@ -52,6 +52,11 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len);
+/// @brief generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+isc::asiolink::IOAddress getNetmask4(uint8_t len);
+
};
};
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 452343c..7a64dac 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,7 +16,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <cstring>
-
+#include <vector>
#include <string.h>
using namespace isc::asiolink;
@@ -44,9 +44,10 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
// Copy the address. It can be either V4 or V6.
std::memcpy(packed, &vec[0], len);
- // Increase the address.
+ // Start increasing the least significant byte
for (int i = len - 1; i >= 0; --i) {
++packed[i];
+ // if we haven't overflowed (0xff -> 0x0), than we are done
if (packed[i] != 0) {
break;
}
@@ -57,7 +58,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
+AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
const DuidPtr&,
const IOAddress&) {
@@ -66,14 +67,14 @@ AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
// perhaps restaring the server).
IOAddress last = subnet->getLastAllocated();
- const Pool6Collection& pools = subnet->getPools();
+ const PoolCollection& pools = subnet->getPools();
if (pools.empty()) {
isc_throw(AllocFailed, "No pools defined in selected subnet");
}
// first we need to find a pool the last address belongs to.
- Pool6Collection::const_iterator it;
+ PoolCollection::const_iterator it;
for (it = pools.begin(); it != pools.end(); ++it) {
if ((*it)->inRange(last)) {
break;
@@ -123,9 +124,9 @@ AllocEngine::HashedAllocator::HashedAllocator()
isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
@@ -136,9 +137,9 @@ AllocEngine::RandomAllocator::RandomAllocator()
isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
@@ -190,7 +191,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
// the hint is valid and not currently used, let's create a lease for it
- Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
// It can happen that the lease allocation failed (we could have lost
// the race condition. That means that the hint is lo longer usable and
@@ -198,9 +199,31 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
if (lease) {
return (lease);
}
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fake_allocation));
+ }
+
}
}
+ // Hint is in the pool but is not available. Search the pool until first of
+ // the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust number of tries
+ //
+ // @todo: Current code does not handle pool exhaustion well. It will be
+ // improved. Current problems:
+ // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+ // 10 addresses), we will iterate over it 100 times before giving up
+ // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+ // 3. the whole concept of infinite attempts is just asking for infinite loop
+ // We may consider some form or reference counting (this pool has X addresses
+ // left), but this has one major problem. We exactly control allocation
+ // moment, but we currently do not control expiration time at all
+
unsigned int i = attempts_;
do {
IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
@@ -209,10 +232,10 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(candidate);
- // there's no existing lease for selected candidate, so it is
- // free. Let's allocate it.
if (!existing) {
- Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
fake_allocation);
if (lease) {
return (lease);
@@ -221,6 +244,11 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
// Although the address was free just microseconds ago, it may have
// been taken just now. If the lease insertion fails, we continue
// allocation attempts.
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fake_allocation));
+ }
}
// continue trying allocation until we run out of attempts
@@ -232,11 +260,230 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
<< " tries");
}
-Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
- const DuidPtr& duid,
- uint32_t iaid,
- const IOAddress& addr,
- bool fake_allocation /*= false */ ) {
+Lease4Ptr
+AllocEngine::allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& hint,
+ bool fake_allocation /* = false */ ) {
+
+ // Allocator is always created in AllocEngine constructor and there is
+ // currently no other way to set it, so that check is not really necessary.
+ if (!allocator_) {
+ isc_throw(InvalidOperation, "No allocator selected");
+ }
+
+ // Check if there's existing lease for that subnet/clientid/hwaddr combination.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID());
+ if (existing) {
+ // We have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ if (existing) {
+ return (existing);
+ }
+
+ // If renewal failed (e.g. the lease no longer matches current configuration)
+ // let's continue the allocation process
+ }
+
+ if (clientid) {
+ existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
+ if (existing) {
+ // we have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ // @todo: produce a warning. We haven't found him using MAC address, but
+ // we found him using client-id
+ if (existing) {
+ return (existing);
+ }
+ }
+ }
+
+ // check if the hint is in pool and is available
+ if (subnet->inPool(hint)) {
+ existing = LeaseMgrFactory::instance().getLease4(hint);
+ if (!existing) {
+ /// @todo: Check if the hint is reserved once we have host support
+ /// implemented
+
+ // The hint is valid and not currently used, let's create a lease for it
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+
+ // It can happen that the lease allocation failed (we could have lost
+ // the race condition. That means that the hint is lo longer usable and
+ // we need to continue the regular allocation path.
+ if (lease) {
+ return (lease);
+ }
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+
+ }
+ }
+
+ // Hint is in the pool but is not available. Search the pool until first of
+ // the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust the number of tries
+ //
+ // @todo: Current code does not handle pool exhaustion well. It will be
+ // improved. Current problems:
+ // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+ // 10 addresses), we will iterate over it 100 times before giving up
+ // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+ // 3. the whole concept of infinite attempts is just asking for infinite loop
+ // We may consider some form or reference counting (this pool has X addresses
+ // left), but this has one major problem. We exactly control allocation
+ // moment, but we currently do not control expiration time at all
+
+ unsigned int i = attempts_;
+ do {
+ IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
+
+ /// @todo: check if the address is reserved once we have host support
+ /// implemented
+
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
+ if (!existing) {
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
+ fake_allocation);
+ if (lease) {
+ return (lease);
+ }
+
+ // Although the address was free just microseconds ago, it may have
+ // been taken just now. If the lease insertion fails, we continue
+ // allocation attempts.
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+ }
+
+ // Continue trying allocation until we run out of attempts
+ // (or attempts are set to 0, which means infinite)
+ --i;
+ } while ( i || !attempts_);
+
+ isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
+ << " tries");
+}
+
+Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */) {
+
+ lease->subnet_id_ = subnet->getID();
+ lease->hwaddr_ = hwaddr->hwaddr_;
+ lease->client_id_ = clientid;
+ lease->cltt_ = time(NULL);
+ lease->t1_ = subnet->getT1();
+ lease->t2_ = subnet->getT2();
+ lease->valid_lft_ = subnet->getValid();
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(lease);
+ }
+
+ return (lease);
+}
+
+Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
+ const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ uint32_t iaid,
+ bool fake_allocation /*= false */ ) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->iaid_ = iaid;
+ expired->duid_ = duid;
+ expired->preferred_lft_ = subnet->getPreferred();
+ expired->valid_lft_ = subnet->getValid();
+ expired->t1_ = subnet->getT1();
+ expired->t2_ = subnet->getT2();
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = subnet->getID();
+ expired->fixed_ = false;
+ expired->hostname_ = std::string("");
+ expired->fqdn_fwd_ = false;
+ expired->fqdn_rev_ = false;
+
+ /// @todo: log here that the lease was reused (there's ticket #2524 for
+ /// logging in libdhcpsrv)
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease6(expired);
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+ const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation /*= false */ ) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->client_id_ = clientid;
+ expired->hwaddr_ = hwaddr->hwaddr_;
+ expired->valid_lft_ = subnet->getValid();
+ expired->t1_ = subnet->getT1();
+ expired->t2_ = subnet->getT2();
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = subnet->getID();
+ expired->fixed_ = false;
+ expired->hostname_ = std::string("");
+ expired->fqdn_fwd_ = false;
+ expired->fqdn_rev_ = false;
+
+ /// @todo: log here that the lease was reused (there's ticket #2524 for
+ /// logging in libdhcpsrv)
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(expired);
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ uint32_t iaid,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
@@ -270,6 +517,53 @@ Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
}
}
+Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
+ const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
+ if (!hwaddr) {
+ isc_throw(BadValue, "Can't create a lease with NULL HW address");
+ }
+ time_t now = time(NULL);
+
+ // @todo: remove this kludge after ticket #2590 is implemented
+ std::vector<uint8_t> local_copy;
+ if (clientid) {
+ local_copy = clientid->getDuid();
+ }
+
+ Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+ &local_copy[0], local_copy.size(), subnet->getValid(),
+ subnet->getT1(), subnet->getT2(), now,
+ subnet->getID()));
+
+ if (!fake_allocation) {
+ // That is a real (REQUEST) allocation
+ bool status = LeaseMgrFactory::instance().addLease(lease);
+ if (status) {
+ return (lease);
+ } else {
+ // One of many failures with LeaseMgr (e.g. lost connection to the
+ // database, database failed etc.). One notable case for that
+ // is that we are working in multi-process mode and we lost a race
+ // (some other process got that address first)
+ return (Lease4Ptr());
+ }
+ } else {
+ // That is only fake (DISCOVER) allocation
+
+ // It is for OFFER only. We should not insert the lease into LeaseMgr,
+ // but rather check that we could have inserted it.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
+ if (!existing) {
+ return (lease);
+ } else {
+ return (Lease4Ptr());
+ }
+ }
+}
+
AllocEngine::~AllocEngine() {
// no need to delete allocator. smart_ptr will do the trick for us
}
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 940b1f4..c6cbc35 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease_mgr.h>
@@ -65,8 +66,14 @@ protected:
/// reserved - AllocEngine will check that and will call pickAddress
/// again if necessary. The number of times this method is called will
/// increase as the number of available leases will decrease.
+ ///
+ /// @param subnet next address will be returned from pool of that subnet
+ /// @param duid Client's DUID
+ /// @param hint client's hint
+ ///
+ /// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint) = 0;
/// @brief virtual destructor
@@ -96,7 +103,7 @@ protected:
/// @param hint client's hint (ignored)
/// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet,
+ pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
private:
@@ -125,7 +132,7 @@ protected:
/// @param duid Client's DUID
/// @param hint a hint (last address that was picked)
/// @return selected address
- virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
+ virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -148,7 +155,7 @@ protected:
/// @param hint the last address that was picked (ignored)
/// @return a random address from the pool
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -174,6 +181,48 @@ protected:
/// we give up (0 means unlimited)
AllocEngine(AllocType engine_type, unsigned int attempts);
+ /// @brief Allocates an IPv4 lease
+ ///
+ /// This method uses currently selected allocator to pick an address from
+ /// specified subnet, creates a lease for that address and then inserts
+ /// it into LeaseMgr (if this allocation is not fake).
+ ///
+ /// @param subnet subnet the allocation should come from
+ /// @param clientid Client identifier
+ /// @param hwaddr client's hardware address info
+ /// @param hint a hint that the client provided
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return Allocated IPv4 lease (or NULL if allocation failed)
+ Lease4Ptr
+ allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& hint,
+ bool fake_allocation);
+
+ /// @brief Renews a IPv4 lease
+ ///
+ /// Since both request and renew are implemented in DHCPv4 as the sending of
+ /// a REQUEST packet, it is difficult to easily distinguish between those
+ /// cases. Therefore renew for DHCPv4 is done in the allocation engine.
+ /// This method is also used when client crashed/rebooted and tries
+ /// to get a new lease. It thinks that it gets a new lease, but in fact
+ /// we are only renewing the still valid lease for that client.
+ ///
+ /// @param subnet subnet the client is attached to
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param lease lease to be renewed
+ /// @param renewed lease (typically the same passed as lease parameter)
+ /// or NULL if the lease cannot be renewed
+ Lease4Ptr
+ renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */);
+
/// @brief Allocates an IPv6 lease
///
/// This method uses currently selected allocator to pick an address from
@@ -181,7 +230,7 @@ protected:
/// it into LeaseMgr (if this allocation is not fake).
///
/// @param subnet subnet the allocation should come from
- /// @param duid Client'd DUID
+ /// @param duid Client's DUID
/// @param iaid iaid field from the IA_NA container that client sent
/// @param hint a hint that the client provided
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
@@ -198,6 +247,25 @@ protected:
virtual ~AllocEngine();
private:
+ /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+ ///
+ /// Creates a lease based on specified parameters and tries to insert it
+ /// into the database. That may fail in some cases, e.g. when there is another
+ /// allocation process and we lost a race to a specific lease.
+ ///
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param addr an address that was selected and is confirmed to be available
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return allocated lease (or NULL in the unlikely case of the lease just
+ /// becomed unavailable)
+ Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
/// @brief creates a lease and inserts it in LeaseMgr if necessary
///
/// Creates a lease based on specified parameters and tries to insert it
@@ -212,9 +280,46 @@ private:
/// an address for SOLICIT that is not really allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
/// becomed unavailable)
- Lease6Ptr createLease(const Subnet6Ptr& subnet, const DuidPtr& duid,
- uint32_t iaid, const isc::asiolink::IOAddress& addr,
- bool fake_allocation = false);
+ Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ uint32_t iaid, const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
+ /// @brief Reuses expired IPv4 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return refreshed lease
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation = false);
+
+ /// @brief Reuses expired IPv6 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. SOLICIT, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param subnet subnet the lease is allocated from
+ /// @param duid client's DUID
+ /// @param iaid IAID from the IA_NA container the client sent to us
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for SOLICIT that is not really allocated (true)
+ /// @return refreshed lease
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
+ const DuidPtr& duid, uint32_t iaid,
+ bool fake_allocation = false);
/// @brief a pointer to currently used allocator
boost::shared_ptr<Allocator> allocator_;
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7dc5f55..ee40130 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.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
@@ -13,7 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
using namespace isc::asiolink;
using namespace isc::util;
@@ -21,15 +23,105 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
-
-
-
CfgMgr&
CfgMgr::instance() {
static CfgMgr cfg_mgr;
return (cfg_mgr);
}
+void
+CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces4_.find(space->getName());
+ if (it != spaces4_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces4_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces6_.find(space->getName());
+ if (it != spaces6_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces6_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space) {
+ // @todo we need better validation of the provided option space name here.
+ // This will be implemented when #2313 is merged.
+ if (option_space.empty()) {
+ isc_throw(BadValue, "option space name must not be empty");
+ } else if (!def) {
+ // Option definition must point to a valid object.
+ isc_throw(MalformedOptionDefinition, "option definition must not be NULL");
+
+ } else if (getOptionDef(option_space, def->getCode())) {
+ // Option definition must not be overriden.
+ isc_throw(DuplicateOptionDefinition, "option definition already added"
+ << " to option space " << option_space);
+
+ } else if ((option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+ (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
+ // We must not override standard (assigned) option. The standard options
+ // belong to dhcp4 or dhcp6 option space.
+ isc_throw(BadValue, "unable to override definition of option '"
+ << def->getCode() << "' in standard option space '"
+ << option_space << "'.");
+
+ }
+ // Actually add a new item.
+ option_def_spaces_.addItem(def, option_space);
+}
+
+OptionDefContainerPtr
+CfgMgr::getOptionDefs(const std::string& option_space) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ return (option_def_spaces_.getItems(option_space));
+}
+
+OptionDefinitionPtr
+CfgMgr::getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ // Get a reference to option definitions for a particular option space.
+ OptionDefContainerPtr defs = getOptionDefs(option_space);
+ // If there are no matching option definitions then return the empty pointer.
+ if (!defs || defs->empty()) {
+ return (OptionDefinitionPtr());
+ }
+ // If there are some option definitions for a particular option space
+ // use an option code to get the one we want.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ // If there is no definition that matches option code, return empty pointer.
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDefinitionPtr());
+ }
+ // If there is more than one definition matching an option code, return
+ // the first one. This should not happen because we check for duplicates
+ // when addOptionDef is called.
+ return (*range.first);
+}
+
Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
@@ -42,6 +134,9 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET6)
+ .arg(subnets6_[0]->toText()).arg(hint.toText());
return (subnets6_[0]);
}
@@ -49,11 +144,16 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
+ .arg(hint.toText());
return (Subnet6Ptr());
}
@@ -65,6 +165,8 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+ .arg(subnet->toText());
subnets6_.push_back(subnet);
}
@@ -80,6 +182,9 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets4_.size() == 1) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET4)
+ .arg(subnets4_[0]->toText()).arg(hint.toText());
return (subnets4_[0]);
}
@@ -87,25 +192,38 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
for (Subnet4Collection::iterator subnet = subnets4_.begin();
subnet != subnets4_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET4)
+ .arg(hint.toText());
return (Subnet4Ptr());
}
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+ .arg(subnet->toText());
subnets4_.push_back(subnet);
}
+void CfgMgr::deleteOptionDefs() {
+ option_def_spaces_.clearItems();
+}
+
void CfgMgr::deleteSubnets4() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
subnets4_.clear();
}
void CfgMgr::deleteSubnets6() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
subnets6_.clear();
}
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index ac1b3f5..9cfde9d 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.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
@@ -17,6 +17,9 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/option_space.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <util/buffer.h>
@@ -77,6 +80,71 @@ public:
/// accessing it.
static CfgMgr& instance();
+ /// @brief Add new option definition.
+ ///
+ /// @param def option definition to be added.
+ /// @param option_space name of the option space to add definition to.
+ ///
+ /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+ /// option definition already exists.
+ /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+ /// an option definition is NULL.
+ /// @throw isc::BadValue when the option space name is empty or
+ /// when trying to override the standard option (in dhcp4 or dhcp6
+ /// option space).
+ void addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space);
+
+ /// @brief Return option definitions for particular option space.
+ ///
+ /// @param option_space option space.
+ ///
+ /// @return pointer to the collection of option definitions for
+ /// the particular option space. The option collection is empty
+ /// if no option exists for the option space specified.
+ OptionDefContainerPtr
+ getOptionDefs(const std::string& option_space) const;
+
+ /// @brief Return option definition for a particular option space and code.
+ ///
+ /// @param option_space option space.
+ /// @param option_code option code.
+ ///
+ /// @return an option definition or NULL pointer if option definition
+ /// has not been found.
+ OptionDefinitionPtr getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const;
+
+ /// @brief Adds new DHCPv4 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace4(const OptionSpacePtr& space);
+
+ /// @brief Adds new DHCPv6 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace6(const OptionSpacePtr& space);
+
+ /// @brief Return option spaces for DHCPv4.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces4() const {
+ return (spaces4_);
+ }
+
+ /// @brief Return option spaces for DHCPv6.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces6() const {
+ return (spaces6_);
+ }
+
/// @brief get IPv6 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
@@ -86,6 +154,8 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
/// @brief get IPv6 subnet by interface-id
@@ -93,12 +163,19 @@ public:
/// Another possibility to find a subnet is based on interface-id.
///
/// @param interface_id content of interface-id option returned by a relay
+ ///
+ /// @return a subnet object
/// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet
+ ///
+ /// @param subnet new subnet to be added.
void addSubnet6(const Subnet6Ptr& subnet);
+ /// @brief Delete all option definitions.
+ void deleteOptionDefs();
+
/// @todo: Add subnet6 removal routines. Currently it is not possible
/// to remove subnets. The only case where subnet6 removal would be
/// needed is a dynamic server reconfiguration - a use case that is not
@@ -125,6 +202,8 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object
Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
/// @brief adds a subnet4
@@ -141,6 +220,7 @@ public:
/// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
/// completely new?
void deleteSubnets4();
+
protected:
/// @brief Protected constructor.
@@ -169,6 +249,22 @@ protected:
/// pattern will use calling inRange() method on each subnet until
/// a match is found.
Subnet4Collection subnets4_;
+
+private:
+
+ /// @brief A collection of option definitions.
+ ///
+ /// A collection of option definitions that can be accessed
+ /// using option space name they belong to.
+ OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> option_def_spaces_;
+
+ /// @brief Container for defined DHCPv6 option spaces.
+ OptionSpaceCollection spaces6_;
+
+ /// @brief Container for defined DHCPv4 option spaces.
+ OptionSpaceCollection spaces4_;
+
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
new file mode 100644
index 0000000..d3d05f6
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -0,0 +1,156 @@
+// 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 DHCP_CONFIG_PARSER_H
+#define DHCP_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if an error occurs while configuring
+/// DHCP server.
+class DhcpConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Forward declaration to DhcpConfigParser class.
+///
+/// It is only needed here to define types that are
+/// based on this class before the class definition.
+class DhcpConfigParser;
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief Collection of parsers.
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+/// @brief Base abstract class for all DHCP parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+class DhcpConfigParser {
+ ///
+ /// @name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+
+ // Private construtor and assignment operator assures that nobody
+ // will be able to copy or assign a parser. There are no defined
+ // bodies for them.
+ DhcpConfigParser(const DhcpConfigParser& source);
+ DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+ /// @brief The default constructor.
+ ///
+ /// This is intentionally defined as @c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ DhcpConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~DhcpConfigParser() {}
+ //@}
+
+ /// @brief Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of @c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// @c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of @c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once in the
+ /// life of the object. Although multiple calls are not prohibited
+ /// by the interface, the behavior is undefined.
+ ///
+ /// @param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// @brief Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in @c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via @c configureDhcp4Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after @c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+
+protected:
+
+ /// @brief Return the parsed entry from the provided storage.
+ ///
+ /// This method returns the parsed entry from the provided
+ /// storage. If the entry is not found, then exception is
+ /// thrown.
+ ///
+ /// @param param_id name of the configuration entry.
+ /// @param storage storage where the entry should be searched.
+ /// @tparam ReturnType type of the returned value.
+ /// @tparam StorageType type of the storage.
+ ///
+ /// @throw DhcpConfigError if the entry has not been found
+ /// in the storage.
+ template<typename ReturnType, typename StorageType>
+ static ReturnType getParam(const std::string& param_id,
+ const StorageType& storage) {
+ typename StorageType::const_iterator param = storage.find(param_id);
+ if (param == storage.end()) {
+ isc_throw(DhcpConfigError, "missing parameter '"
+ << param_id << "'");
+ }
+ ReturnType value = param->second;
+ return (value);
+ }
+
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..6cbfb0d
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..9b6350a
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+
+/// \brief DHCP server library Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..27f12fc
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,231 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv4
+subnets in its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv6
+subnets in its database.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_COMMIT commiting to memory file database
+The code has issued a commit call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database. The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the memory file database for a client with the specified
+IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_COMMIT commiting to MySQl database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the commital may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the MySQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string. The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index f7b6373..6608b14 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.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
@@ -32,13 +32,18 @@ using namespace std;
namespace isc {
namespace dhcp {
+Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt)
+ :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt),
+ subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false) {
+}
+
Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
- : addr_(addr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
- preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
- subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
- fqdn_rev_(false) {
+ : Lease(addr, t1, t2, valid, subnet_id, 0/*cltt*/),
+ type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+ preferred_lft_(preferred) {
if (!duid) {
isc_throw(InvalidOperation, "DUID must be specified for a lease");
}
@@ -46,6 +51,14 @@ Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
cltt_ = time(NULL);
}
+bool Lease::expired() const {
+
+ // Let's use int64 to avoid problems with negative/large uint32 values
+ int64_t expire_time = cltt_ + valid_lft_;
+ return (expire_time < time(NULL));
+}
+
+
std::string LeaseMgr::getParameter(const std::string& name) const {
ParameterMap::const_iterator param = parameters_.find(name);
if (param == parameters_.end()) {
@@ -55,7 +68,7 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
}
std::string
-Lease6::toText() {
+Lease6::toText() const {
ostringstream stream;
stream << "Type: " << static_cast<int>(type_) << " (";
@@ -83,6 +96,21 @@ Lease6::toText() {
return (stream.str());
}
+std::string
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_.toText() << "\n"
+ << "Valid life: " << valid_lft_ << "\n"
+ << "T1: " << t1_ << "\n"
+ << "T2: " << t2_ << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n";
+
+ return (stream.str());
+}
+
+
bool
Lease4::operator==(const Lease4& other) const {
return (
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 7865e11..f781576 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -25,6 +26,7 @@
#include <boost/shared_ptr.hpp>
#include <fstream>
+#include <iostream>
#include <map>
#include <string>
#include <utility>
@@ -61,8 +63,6 @@
/// Nevertheless, we hope to have failover protocol eventually implemented in
/// the Kea.
-#include <iostream>
-
namespace isc {
namespace dhcp {
@@ -108,38 +108,21 @@ public:
isc::Exception(file, line, what) {}
};
-/// @brief Structure that holds a lease for IPv4 address
+/// @brief a common structure for IPv4 and IPv6 leases
///
-/// For performance reasons it is a simple structure, not a class. If we chose
-/// make it a class, all fields would have to made private and getters/setters
-/// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access is warranted.
-struct Lease4 {
- /// @brief Maximum size of a hardware address
- static const size_t HWADDR_MAX = 20;
+/// This structure holds all information that is common between IPv4 and IPv6
+/// leases.
+struct Lease {
+ Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt);
- /// IPv4 address
- isc::asiolink::IOAddress addr_;
+ virtual ~Lease() {}
- /// @brief Address extension
+ /// @brief IPv4 ot IPv6 address
///
- /// It is envisaged that in some cases IPv4 address will be accompanied
- /// with some additional data. One example of such use are Address + Port
- /// solutions (or Port-restricted Addresses), where several clients may get
- /// the same address, but different port ranges. This feature is not
- /// expected to be widely used. Under normal circumstances, the value
- /// should be 0.
- uint32_t ext_;
-
- /// @brief Hardware address
- std::vector<uint8_t> hwaddr_;
-
- /// @brief Client identifier
- ///
- /// @todo Should this be a pointer to a client ID or the ID itself?
- /// Compare with the DUID in the Lease6 structure.
- ClientIdPtr client_id_;
+ /// IPv4, IPv6 address or, in the case of a prefix delegation, the prefix.
+ isc::asiolink::IOAddress addr_;
/// @brief Renewal timer
///
@@ -201,6 +184,46 @@ struct Lease4 {
/// system administrator.
std::string comments_;
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const = 0;
+
+ /// @brief returns true if the lease is expired
+ /// @return true if the lease is expired
+ bool expired() const;
+
+};
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 : public Lease {
+ /// @brief Maximum size of a hardware address
+ static const size_t HWADDR_MAX = 20;
+
+ /// @brief Address extension
+ ///
+ /// It is envisaged that in some cases IPv4 address will be accompanied
+ /// with some additional data. One example of such use are Address + Port
+ /// solutions (or Port-restricted Addresses), where several clients may get
+ /// the same address, but different port ranges. This feature is not
+ /// expected to be widely used. Under normal circumstances, the value
+ /// should be 0.
+ uint32_t ext_;
+
+ /// @brief Hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ /// @brief Client identifier
+ ///
+ /// @todo Should this be a pointer to a client ID or the ID itself?
+ /// Compare with the DUID in the Lease6 structure.
+ ClientIdPtr client_id_;
+
/// @brief Constructor
///
/// @param addr IPv4 address as unsigned 32-bit integer in network byte
@@ -212,21 +235,19 @@ struct Lease4 {
/// @param valid_lft Lifetime of the lease
/// @param cltt Client last transmission time
/// @param subnet_id Subnet identification
- Lease4(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
+ Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len,
const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
- time_t cltt, uint32_t subnet_id)
- : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
- client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
- valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
- fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
- comments_()
- {}
+ uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
+ : Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
+ ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+ client_id_(new ClientId(clientid, clientid_len)) {
+ }
- /// @brief Default Constructor
+ /// @brief Default constructor
///
/// Initialize fields that don't have a default constructor.
- Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
- {}
+ Lease4() : Lease(0, 0, 0, 0, 0, 0) {
+ }
/// @brief Compare two leases for equality
///
@@ -240,6 +261,11 @@ struct Lease4 {
return (!operator==(other));
}
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual represenation of lease data
+ virtual std::string toText() const;
+
/// @todo: Add DHCPv4 failover related fields here
};
@@ -257,7 +283,7 @@ typedef std::vector<Lease4Ptr> Lease4Collection;
/// make it a class, all fields would have to made private and getters/setters
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access is warranted.
-struct Lease6 {
+struct Lease6 : public Lease {
/// @brief Type of lease contents
typedef enum {
@@ -266,11 +292,6 @@ struct Lease6 {
LEASE_IA_PD /// the lease contains IPv6 prefix (for prefix delegation)
} LeaseType;
- /// @brief IPv6 address
- ///
- /// IPv6 address or, in the case of a prefix delegation, the prefix.
- isc::asiolink::IOAddress addr_;
-
/// @brief Lease type
///
/// One of normal address, temporary address, or prefix.
@@ -297,69 +318,6 @@ struct Lease6 {
/// assigned or renewed (cltt), expressed in seconds.
uint32_t preferred_lft_;
- /// @brief valid lifetime
- ///
- /// This parameter specifies the valid lifetime since the lease waa
- /// assigned/renewed (cltt), expressed in seconds.
- uint32_t valid_lft_;
-
- /// @brief T1 timer
- ///
- /// Specifies renewal time. Although technically it is a property of the
- /// IA container and not the address itself, since our data model does not
- /// define a separate IA entity, we are keeping it in the lease. In the
- /// case of multiple addresses/prefixes for the same IA, each must have
- /// consistent T1 and T2 values. This is specified in seconds since cltt.
- /// The value will also be useful for failover to calculate the next
- /// expected client transmission time.
- uint32_t t1_;
-
- /// @brief T2 timer
- ///
- /// Specifies rebinding time. Although technically it is a property of the
- /// IA container and not the address itself, since our data model does not
- /// define a separate IA entity, we are keeping it in the lease. In the
- /// case of multiple addresses/prefixes for the same IA, each must have
- /// consistent T1 and T2 values. This is specified in seconds since cltt.
- uint32_t t2_;
-
- /// @brief Client last transmission time
- ///
- /// Specifies a timestamp giving the time when the last transmission from a
- /// client was received.
- time_t cltt_;
-
- /// @brief Subnet identifier
- ///
- /// Specifies the identification of the subnet to which the lease belongs.
- SubnetID subnet_id_;
-
- /// @brief Fixed lease?
- ///
- /// Fixed leases are kept after they are released/expired.
- bool fixed_;
-
- /// @brief Client hostname
- ///
- /// This field may be empty
- std::string hostname_;
-
- /// @brief Forward zone updated?
- ///
- /// Set true if the DNS AAAA record for this lease has been updated.
- bool fqdn_fwd_;
-
- /// @brief Reverse zone updated?
- ///
- /// Set true if the DNS PTR record for this lease has been updated.
- bool fqdn_rev_;
-
- /// @brief Lease comments
- ///
- /// Currently not used. It may be used for keeping comments made by the
- /// system administrator.
- std::string comments_;
-
/// @todo: Add DHCPv6 failover related fields here
/// @brief Constructor
@@ -370,14 +328,9 @@ struct Lease6 {
/// @brief Constructor
///
/// Initialize fields that don't have a default constructor.
- Lease6() : addr_("::"), type_(LEASE_IA_NA), fixed_(false), fqdn_fwd_(false),
- fqdn_rev_(false)
- {}
-
- /// @brief Convert Lease6 to Printable Form
- ///
- /// @return String form of the lease
- std::string toText();
+ Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0),
+ type_(LEASE_IA_NA) {
+ }
/// @brief Compare two leases for equality
///
@@ -390,6 +343,11 @@ struct Lease6 {
bool operator!=(const Lease6& other) const {
return (!operator==(other));
}
+
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const;
};
/// @brief Pointer to a Lease6 structure.
@@ -413,9 +371,6 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// see the documentation of those classes for details.
class LeaseMgr {
public:
- /// Client hardware address
- typedef std::vector<uint8_t> HWAddr;
-
/// Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
@@ -470,7 +425,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -482,7 +437,7 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const = 0;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 47c53ed..9fd276d 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -14,6 +14,7 @@
#include "config.h"
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#ifdef HAVE_MYSQL
@@ -43,17 +44,17 @@ LeaseMgrFactory::getLeaseMgrPtr() {
}
LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbconfig) {
+LeaseMgrFactory::parse(const std::string& dbaccess) {
LeaseMgr::ParameterMap mapped_tokens;
- if (!dbconfig.empty()) {
+ if (!dbaccess.empty()) {
vector<string> tokens;
// We need to pass a string to is_any_of, not just char*. Otherwise
// there are cryptic warnings on Debian6 running g++ 4.4 in
// /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
// array bounds"
- boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+ boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
BOOST_FOREACH(std::string token, tokens) {
size_t pos = token.find("=");
if (pos != string::npos) {
@@ -61,6 +62,7 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
string value = token.substr(pos + 1);
mapped_tokens.insert(make_pair(name, value));
} else {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
isc_throw(InvalidParameter, "Cannot parse " << token
<< ", expected format is name=value");
}
@@ -70,31 +72,70 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
return (mapped_tokens);
}
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+ // Reconstruct the access string: start of with an empty string, then
+ // work through all the parameters in the original string and add them.
+ std::string access;
+ for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens are preceded by a space.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter...
+ access += i->first;
+ access += "=";
+
+ // ... and the value, except in the case of the password, where a
+ // redacted value is appended.
+ if (i->first == std::string("password")) {
+ access += "*****";
+ } else {
+ access += i->second;
+ }
+ }
+
+ return (access);
+}
+
void
-LeaseMgrFactory::create(const std::string& dbconfig) {
+LeaseMgrFactory::create(const std::string& dbaccess) {
const std::string type = "type";
+ // Parse the access string and create a redacted string for logging.
+ LeaseMgr::ParameterMap parameters = parse(dbaccess);
+ std::string redacted = redactedAccessString(parameters);
+
// Is "type" present?
- LeaseMgr::ParameterMap parameters = parse(dbconfig);
if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
isc_throw(InvalidParameter, "Database configuration parameters do not "
"contain the 'type' keyword");
}
+
// Yes, check what it is.
#ifdef HAVE_MYSQL
if (parameters[type] == string("mysql")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
#endif
if (parameters[type] == string("memfile")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
return;
}
// Get here on no match
- isc_throw(InvalidType, "Database configuration parameter 'type' does "
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
"not specify a supported database backend");
}
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
index 338c941..9218cf5 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.h
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -66,21 +66,21 @@ public:
/// @note When called, the current lease manager is <b>always</b> destroyed
/// and a new one created - even if the parameters are the same.
///
- /// dbconfig is a generic way of passing parameters. Parameters are passed
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
/// in the "name=value" format, separated by spaces. The data MUST include
/// a keyword/value pair of the form "type=dbtype" giving the database
/// type, e.q. "mysql" or "sqlite3".
///
- /// @param dbconfig Database configuration parameters. These are in
- /// the form of "keyword=value" pairs, separated by spaces. These
- /// are back-end specific, although must include the "type" keyword
- /// which gives the backend in use.
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
///
- /// @throw isc::InvalidParameter dbconfig string does not contain the "type"
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
/// keyword.
- /// @throw isc::dhcp::InvalidType The "type" keyword in dbconfig does not
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
/// identify a supported backend.
- static void create(const std::string& dbconfig);
+ static void create(const std::string& dbaccess);
/// @brief Destroy lease manager
///
@@ -89,7 +89,7 @@ public:
/// lease manager is available.
static void destroy();
- /// @brief Return Current Lease Manager
+ /// @brief Return current lease manager
///
/// Returns an instance of the "current" lease manager. An exception
/// will be thrown if none is available.
@@ -98,15 +98,26 @@ public:
/// create() to create one before calling this method.
static LeaseMgr& instance();
- /// @brief Parse Database Parameters
+ /// @brief Parse database access string
///
/// Parses the string of "keyword=value" pairs and separates them
/// out into the map.
///
- /// @param dbconfig Database configuration string
+ /// @param dbaccess Database access string.
///
/// @return std::map<std::string, std::string> Map of keyword/value pairs.
- static LeaseMgr::ParameterMap parse(const std::string& dbconfig);
+ static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
+
+ /// @brief Redact database access string
+ ///
+ /// Takes the database parameters and returns a database access string
+ /// passwords replaced by asterisks. This string is used in log messages.
+ ///
+ /// @param parameters Database access parameters (output of "parse").
+ ///
+ /// @return Redacted database access string.
+ static std::string redactedAccessString(
+ const LeaseMgr::ParameterMap& parameters);
private:
/// @brief Hold pointer to lease manager
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index b71b166..34f21e9 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -12,7 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <exceptions/exceptions.h>
#include <iostream>
@@ -20,19 +22,29 @@ using namespace isc::dhcp;
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
: LeaseMgr(parameters) {
- std::cout << "Warning: Using memfile database backend. It is usable for" << std::endl;
- std::cout << "Warning: limited testing only. File support not implemented yet." << std::endl;
- std::cout << "Warning: Leases will be lost after restart." << std::endl;
+ std::cout << "Warning: Using memfile database backend. It is usable for limited"
+ << " testing only. Leases will be lost after restart." << std::endl;
}
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
}
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
- return (false);
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
+
+ if (getLease4(lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+ storage4_.insert(lease);
+ return (true);
}
bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
if (getLease6(lease->addr_)) {
// there is a lease with specified address already
return (false);
@@ -41,30 +53,71 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
return (true);
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
- return (Lease4Ptr());
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ return (Lease4Ptr());
+ } else {
+ return (*l);
+ }
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
- return (Lease4Collection());
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
+
+ isc_throw(NotImplemented, "getLease4(HWaddr x) method not implemented yet");
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( ((*l)->hwaddr_ == hwaddr.hwaddr_) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+
+ // not found
return (Lease4Ptr());
}
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
+ isc_throw(NotImplemented, "getLease4(ClientId) not implemented");
+}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+ .arg(client_id.toText());
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( (*(*l)->client_id_ == client_id) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+
+ // not found
return (Lease4Ptr());
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
- return (Lease4Collection());
-}
+Lease6Ptr Memfile_LeaseMgr::getLease6(
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
return (Lease6Ptr());
@@ -73,12 +126,20 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) cons
}
}
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+
return (Lease6Collection());
}
Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
+
/// @todo: Slow, naive implementation. Write it using additional indexes
for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
if ( (*((*l)->duid_) == duid) &&
@@ -90,20 +151,35 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
return (Lease6Ptr());
}
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
}
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
+
}
bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
if (addr.isV4()) {
- // V4 not implemented yet
- return (false);
+ // v4 lease
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ storage4_.erase(l);
+ return (true);
+ }
} else {
- // V6 lease
+ // v6 lease
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
// No such lease
@@ -123,8 +199,11 @@ std::string Memfile_LeaseMgr::getDescription() const {
void
Memfile_LeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
}
void
Memfile_LeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ROLLBACK);
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 268b722..81e5fe3 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MEMFILE_LEASE_MGR_H
#define MEMFILE_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index/indexed_by.hpp>
@@ -80,7 +81,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -225,12 +226,28 @@ protected:
// IPv6 address that are unique. That particular key is a member
// of the Lease6 structure, is of type IOAddress and can be accessed
// by doing &Lease6::addr_
- boost::multi_index::ordered_unique<
- boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_>
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
>
>
> Lease6Storage; // Let the whole contraption be called Lease6Storage.
+ typedef boost::multi_index_container< // this is a multi-index container...
+ Lease4Ptr, // it will hold shared_ptr to leases6
+ boost::multi_index::indexed_by< // and will be sorted by
+ // IPv6 address that are unique. That particular key is a member
+ // of the Lease6 structure, is of type IOAddress and can be accessed
+ // by doing &Lease6::addr_
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >
+ >
+ > Lease4Storage; // Let the whole contraption be called Lease6Storage.
+
+ /// @brief stores IPv4 leases
+ Lease4Storage storage4_;
+
+ /// @brief stores IPv6 leases
Lease6Storage storage6_;
};
@@ -238,4 +255,3 @@ protected:
}; // end of isc namespace
#endif // MEMFILE_LEASE_MGR
-
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index ad6e66c..292df61 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,14 +16,16 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
#include <mysql/mysqld_error.h>
-#include <algorithm>
#include <iostream>
#include <iomanip>
+#include <sstream>
#include <string>
#include <time.h>
@@ -193,6 +195,8 @@ TaggedStatement tagged_statements[] = {
}; // Anonymous namespace
+
+
namespace isc {
namespace dhcp {
@@ -448,9 +452,10 @@ public:
time_t cltt = 0;
MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ // note that T1 and T2 are not stored
return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
client_id_buffer_, client_id_length_,
- valid_lifetime_, cltt, subnet_id_)));
+ valid_lifetime_, 0, 0, cltt, subnet_id_)));
}
/// @brief Return columns in error
@@ -1112,6 +1117,9 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
bool
MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR4).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1121,6 +1129,9 @@ MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
bool
MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1257,6 +1268,9 @@ void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
Lease4Ptr
MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR4).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1276,6 +1290,9 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_HWADDR).arg(hwaddr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1285,8 +1302,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
// a "char*". (We could avoid the "const_cast" by copying the data to a
// local variable, but as the data is only being read, this introduces
// an unnecessary copy).
- unsigned long hwaddr_length = hwaddr.size();
- uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = reinterpret_cast<char*>(data);
@@ -1303,6 +1320,10 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hwaddr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1312,8 +1333,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
// a "char*". (We could avoid the "const_cast" by copying the data to a
// local variable, but as the data is only being read, this introduces
// an unnecessary copy).
- unsigned long hwaddr_length = hwaddr.size();
- uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = reinterpret_cast<char*>(data);
@@ -1334,6 +1355,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_CLIENTID).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1355,6 +1379,10 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1380,6 +1408,9 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR6).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1403,6 +1434,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Collection
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_DUID).arg(iaid).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -1444,6 +1477,9 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
@@ -1511,6 +1547,9 @@ void
MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE4;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1533,6 +1572,9 @@ void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1579,6 +1621,8 @@ MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
bool
MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_DELETE_ADDR).arg(addr.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
@@ -1632,6 +1676,9 @@ std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion() const {
const StatementIndex stindex = GET_VERSION;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
uint32_t major; // Major version number
uint32_t minor; // Minor version number
@@ -1678,6 +1725,7 @@ MySqlLeaseMgr::getVersion() const {
void
MySqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
if (mysql_commit(mysql_) != 0) {
isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
}
@@ -1686,6 +1734,7 @@ MySqlLeaseMgr::commit() {
void
MySqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
if (mysql_rollback(mysql_) != 0) {
isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 68b0e1c..62f4435 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MYSQL_LEASE_MGR_H
#define MYSQL_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
@@ -131,7 +132,7 @@ public:
/// programming error.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -149,7 +150,7 @@ public:
/// programming error.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
new file mode 100644
index 0000000..0e802a7
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.cc
@@ -0,0 +1,71 @@
+// 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+ : name_(name), vendor_space_(vendor_space) {
+ // Check that provided option space name is valid.
+ if (!validateName(name_)) {
+ isc_throw(InvalidOptionSpace, "Invalid option space name "
+ << name_);
+ }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+ using namespace boost::algorithm;
+
+ // Allowed characters are: lower or upper case letters, digits,
+ // underscores and hyphens. Empty option spaces are not allowed.
+ if (all(name, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) &&
+ !name.empty() &&
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+ !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
+ return (true);
+
+ }
+ return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+ : OptionSpace(name),
+ enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+ const uint32_t enterprise_number)
+ : OptionSpace(name, true),
+ enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+ enterprise_number_ = enterprise_number;
+ OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/option_space.h b/src/lib/dhcpsrv/option_space.h
new file mode 100644
index 0000000..9eebd76
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.h
@@ -0,0 +1,189 @@
+// 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
+// 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_SPACE_H
+#define OPTION_SPACE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+ InvalidOptionSpace(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param name option space name.
+ /// @param vendor_space boolean value that indicates that the object
+ /// describes the vendor specific option space.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref validateName function to check that the specified name is
+ /// correct.
+ OptionSpace(const std::string& name, const bool vendor_space = false);
+
+ /// @brief Return option space name.
+ ///
+ /// @return option space name.
+ const std::string& getName() const { return (name_); }
+
+ /// @brief Mark option space as non-vendor space.
+ void clearVendorSpace() {
+ vendor_space_ = false;
+ }
+
+ /// @brief Check if option space is vendor specific.
+ ///
+ /// @return boolean value that indicates if the object describes
+ /// the vendor specific option space.
+ bool isVendorSpace() const { return (vendor_space_); }
+
+ /// @brief Mark option space as vendor specific.
+ void setVendorSpace() {
+ vendor_space_ = true;
+ }
+
+ /// @brief Checks that the provided option space name is valid.
+ ///
+ /// It is expected that option space name consists of upper or
+ /// lower case letters or digits. Also, it may contain underscores
+ /// or dashes. Other characters are prohibited. The empty option
+ /// space names are invalid.
+ ///
+ /// @param name option space name to be validated.
+ ///
+ /// @return true if the option space is valid, else it returns false.
+ static bool validateName(const std::string& name);
+
+private:
+ std::string name_; ///< Holds option space name.
+
+ bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC3315.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+ /// @brief Constructor for non-vendor-specific options.
+ ///
+ /// This constructor marks option space as non-vendor specific.
+ ///
+ /// @param name option space name.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name);
+
+ /// @brief Constructor for vendor-specific options.
+ ///
+ /// This constructor marks option space as vendor specific and sets
+ /// enterprise number to a given value.
+ ///
+ /// @param name option space name.
+ /// @param enterprise_number enterprise number.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+ /// @brief Return enterprise number for the option space.
+ ///
+ /// @return enterprise number.
+ uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+ /// @brief Mark option space as vendor specific.
+ ///
+ /// @param enterprise_number enterprise number.
+ void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+
+ uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
new file mode 100644
index 0000000..f90bedd
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -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.
+
+#ifndef OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types in
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+template<typename ContainerType, typename ItemType>
+class OptionSpaceContainer {
+public:
+
+ /// Pointer to the container.
+ typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+ /// @brief Adds a new item to the option_space.
+ ///
+ /// @param item reference to the item being added.
+ /// @param name of the option space.
+ void addItem(const ItemType& item, const std::string& option_space) {
+ ItemsContainerPtr items = getItems(option_space);
+ items->push_back(item);
+ option_space_map_[option_space] = items;
+ }
+
+ /// @brief Get all items for the particular option space.
+ ///
+ /// @warning when there are no items for the specified option
+ /// space an empty container is created and returned. However
+ /// this container is not added to the list of option spaces.
+ ///
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to the container holding items.
+ ItemsContainerPtr getItems(const std::string& option_space) const {
+ const typename OptionSpaceMap::const_iterator& items =
+ option_space_map_.find(option_space);
+ if (items == option_space_map_.end()) {
+ return (ItemsContainerPtr(new ContainerType()));
+ }
+ return (items->second);
+ }
+
+ /// @brief Get a list of existing option spaces.
+ ///
+ /// @return a list of option spaces.
+ ///
+ /// @todo This function is likely to be removed once
+ /// we create a structore of OptionSpaces defined
+ /// through the configuration manager.
+ std::list<std::string> getOptionSpaceNames() {
+ std::list<std::string> names;
+ for (typename OptionSpaceMap::const_iterator space =
+ option_space_map_.begin();
+ space != option_space_map_.end(); ++space) {
+ names.push_back(space->first);
+ }
+ return (names);
+ }
+
+ /// @brief Remove all items from the container.
+ void clearItems() {
+ option_space_map_.clear();
+ }
+
+private:
+
+ /// A map holding container (option space name is the key).
+ typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+ OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index eb5e5e2..e8dc3e3 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -179,6 +179,13 @@ typedef boost::shared_ptr<Pool6> Pool6Ptr;
/// @brief a container for IPv6 Pools
typedef std::vector<Pool6Ptr> Pool6Collection;
+/// @brief a pointer to either IPv4 or IPv6 Pool
+typedef boost::shared_ptr<Pool> PoolPtr;
+
+/// @brief a container for either IPv4 or IPv6 Pools
+typedef std::vector<PoolPtr> PoolCollection;
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3f8733f..6414f88 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.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
@@ -44,14 +44,45 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
}
void
-Subnet::addOption(OptionPtr& option, bool persistent /* = false */) {
+Subnet::addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space) {
+ // @todo Once the #2313 is merged we need to use the OptionSpace object to
+ // validate the option space name here. For now, let's check that the name
+ // is not empty as the empty namespace has a special meaning here - it is
+ // returned when desired namespace is not found when getOptions is called.
+ if (option_space.empty()) {
+ isc_throw(isc::BadValue, "option space name must not be empty");
+ }
validateOption(option);
- options_.push_back(OptionDescriptor(option, persistent));
+
+ // Actually add new option descriptor.
+ option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
}
void
Subnet::delOptions() {
- options_.clear();
+ option_spaces_.clearItems();
+}
+
+Subnet::OptionContainerPtr
+Subnet::getOptionDescriptors(const std::string& option_space) const {
+ return (option_spaces_.getItems(option_space));
+}
+
+Subnet::OptionDescriptor
+Subnet::getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code) {
+ OptionContainerPtr options = getOptionDescriptors(option_space);
+ if (!options || options->empty()) {
+ return (OptionDescriptor(false));
+ }
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ const OptionContainerTypeRange& range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDescriptor(false));
+ }
+
+ return (*range.first);
}
std::string Subnet::toText() const {
@@ -71,14 +102,14 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet4::addPool4(const Pool4Ptr& pool) {
+void Subnet::addPool(const PoolPtr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet4");
+ isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+ << " does not belong in this (" << prefix_.toText() << "/"
+ << static_cast<int>(prefix_len_) << ") subnet4");
}
/// @todo: Check that pools do not overlap
@@ -86,9 +117,10 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
pools_.push_back(pool);
}
-Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool4Ptr candidate;
- for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
+
+ PoolPtr candidate;
+ for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
// if we won't find anything better, then let's just use the first pool
if (!candidate) {
@@ -104,6 +136,7 @@ Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("
return (candidate);
}
+
void
Subnet4::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -113,14 +146,14 @@ Subnet4::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
if (!inRange(addr)) {
return (false);
}
- for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
if ((*pool)->inRange(addr)) {
return (true);
}
@@ -142,39 +175,6 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet6::addPool6(const Pool6Ptr& pool) {
- IOAddress first_addr = pool->getFirstAddress();
- IOAddress last_addr = pool->getLastAddress();
-
- if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet6");
- }
-
- /// @todo: Check that pools do not overlap
-
- pools_.push_back(pool);
-}
-
-Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool6Ptr candidate;
- for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-
- // if we won't find anything better, then let's just use the first pool
- if (!candidate) {
- candidate = *pool;
- }
-
- // if the client provided a pool and there's a pool that hint is valid in,
- // then let's use that pool
- if ((*pool)->inRange(hint)) {
- return (*pool);
- }
- }
- return (candidate);
-}
-
void
Subnet6::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -184,21 +184,5 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
-
- // Let's start with checking if it even belongs to that subnet.
- if (!inRange(addr)) {
- return (false);
- }
-
- for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
- if ((*pool)->inRange(addr)) {
- return (true);
- }
- }
- // there's no pool that address belongs to
- return (false);
-}
-
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index c7d7ac7..b864321 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.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
@@ -24,6 +24,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/triplet.h>
@@ -78,6 +79,9 @@ public:
: option(OptionPtr()), persistent(persist) {};
};
+ /// A pointer to option descriptor.
+ typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
/// @brief Extractor class to extract key with another key.
///
/// This class solves the problem of accessing index key values
@@ -198,6 +202,8 @@ public:
>
> OptionContainer;
+ // Pointer to the OptionContainer object.
+ typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
/// Type of the index #1 - option type.
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Pair of iterators to represent the range of options having the
@@ -216,9 +222,11 @@ public:
/// @param option option instance.
/// @param persistent if true, send an option regardless if client
/// requested it or not.
+ /// @param option_space name of the option space to add an option to.
///
/// @throw isc::BadValue if invalid option provided.
- void addOption(OptionPtr& option, bool persistent = false);
+ void addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space);
/// @brief Delete all options configured for the subnet.
void delOptions();
@@ -235,7 +243,7 @@ public:
/// @param addr this address will be checked if it belongs to any pools in
/// that subnet
/// @return true if the address is in any of the pools
- virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+ bool inPool(const isc::asiolink::IOAddress& addr) const;
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
@@ -252,14 +260,24 @@ public:
return (t2_);
}
- /// @brief Return a collection of options.
+ /// @brief Return a collection of option descriptors.
///
- /// @return reference to collection of options configured for a subnet.
- /// The returned reference is valid as long as the Subnet object which
- /// returned it still exists.
- const OptionContainer& getOptions() const {
- return (options_);
- }
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to collection of options configured for a subnet.
+ OptionContainerPtr
+ getOptionDescriptors(const std::string& option_space) const;
+
+ /// @brief Return single option descriptor.
+ ///
+ /// @param option_space name of the option space.
+ /// @param option_code code of the option to be returned.
+ ///
+ /// @return option descriptor found for the specified option space
+ /// and option code.
+ OptionDescriptor
+ getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code);
/// @brief returns the last address that was tried from this pool
///
@@ -298,6 +316,37 @@ public:
return (std::make_pair(prefix_, prefix_len_));
}
+ /// @brief Adds a new pool.
+ /// @param pool pool to be added
+ void addPool(const PoolPtr& pool);
+
+ /// @brief Returns a pool that specified address belongs to
+ ///
+ /// @param addr address that the returned pool should cover (optional)
+ /// @return Pointer to found Pool4 or Pool6 (or NULL)
+ PoolPtr getPool(isc::asiolink::IOAddress addr);
+
+ /// @brief Returns a pool without any address specified
+ /// @return returns one of the pools defined
+ PoolPtr getPool() {
+ return (getPool(default_pool()));
+ }
+
+ /// @brief Returns the default address that will be used for pool selection
+ ///
+ /// It must be implemented in derived classes (should return :: for Subnet6
+ /// and 0.0.0.0 for Subnet4)
+ virtual isc::asiolink::IOAddress default_pool() const = 0;
+
+ /// @brief returns all pools
+ ///
+ /// The reference is only valid as long as the object that returned it.
+ ///
+ /// @return a collection of all pools
+ const PoolCollection& getPools() const {
+ return pools_;
+ }
+
/// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
///
/// @return textual representation
@@ -338,6 +387,9 @@ protected:
/// a Subnet4 or Subnet6.
SubnetID id_;
+ /// @brief collection of pools in that list
+ PoolCollection pools_;
+
/// @brief a prefix of the subnet
isc::asiolink::IOAddress prefix_;
@@ -353,9 +405,6 @@ protected:
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
- /// @brief a collection of DHCP options configured for a subnet.
- OptionContainer options_;
-
/// @brief last allocated address
///
/// This is the last allocated address that was previously allocated from
@@ -366,8 +415,19 @@ protected:
/// that purpose it should be only considered a help that should not be
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
+
+private:
+
+ /// A collection of option spaces grouping option descriptors.
+ typedef OptionSpaceContainer<OptionContainer,
+ OptionDescriptor> OptionSpaceCollection;
+ OptionSpaceCollection option_spaces_;
+
};
+/// @brief A generic pointer to either Subnet4 or Subnet6 object
+typedef boost::shared_ptr<Subnet> SubnetPtr;
+
/// @brief A configuration holder for IPv4 subnet.
///
/// This class represents an IPv4 subnet.
@@ -386,34 +446,6 @@ public:
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
- /// @brief Returns a pool that specified address belongs to
- ///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool4 (or NULL)
- Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("0.0.0.0"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool4(const Pool4Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that returned it.
- ///
- /// @return a collection of all pools
- const Pool4Collection& getPools() const {
- return pools_;
- }
-
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
-
protected:
/// @brief Check if option is valid and can be added to a subnet.
@@ -423,8 +455,11 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
- /// @brief collection of pools in that list
- Pool4Collection pools_;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv4 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("0.0.0.0"));
+ }
};
/// @brief A pointer to a Subnet4 object
@@ -461,35 +496,6 @@ public:
return (preferred_);
}
- /// @brief Returns a pool that specified address belongs to
- ///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool6 (or NULL)
- Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("::"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool6(const Pool6Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that
- /// returned it.
- ///
- /// @return a collection of all pools
- const Pool6Collection& getPools() const {
- return pools_;
- }
-
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
-
protected:
/// @brief Check if option is valid and can be added to a subnet.
@@ -499,6 +505,12 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv6 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("::"));
+ }
+
/// @brief collection of pools in that list
Pool6Collection pools_;
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 8de5fda..dfe3e78 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -36,10 +36,12 @@ libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
+libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
libdhcpsrv_unittests_SOURCES += schema_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
+libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h
libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
if HAVE_MYSQL
diff --git a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
index 19f08a9..cdd59df 100644
--- a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
+++ b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
@@ -16,6 +16,7 @@
#include <config.h>
#include <dhcpsrv/addr_utilities.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -28,6 +29,8 @@ using namespace std;
using namespace isc::dhcp;
using namespace isc::asiolink;
+namespace {
+
// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
IOAddress addr1("192.0.2.1");
@@ -154,3 +157,47 @@ TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
}
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+ EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+ EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+ EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+ EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+ EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+ EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+ EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+ EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+ EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+ EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+ EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+ EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+ EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+ EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+ EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+ EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+ EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+ EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+ EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+ EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+ EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+ EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+ EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+ EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+ EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+ EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+ EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+ EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+ EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+ EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+ EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+ EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+ EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+ EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index dcfb68b..078998d 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -16,41 +16,59 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/tests/test_utils.h>
+
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
#include <sstream>
-#include <map>
+#include <set>
+#include <time.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::dhcp::test;
namespace {
+/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
public:
+
+ /// @brief the sole constructor
+ /// @param engine_type specifies engine type (e.g. iterative)
+ /// @param attempts number of lease selection attempts before giving up
NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts)
:AllocEngine(engine_type, attempts) {
}
+
+ // Expose internal classes for testing purposes
using AllocEngine::Allocator;
using AllocEngine::IterativeAllocator;
};
-// empty class for now, but may be extended once Addr6 becomes bigger
-class AllocEngineTest : public ::testing::Test {
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
public:
- AllocEngineTest() {
- duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+
+ /// @brief Default constructor
+ ///
+ /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+ /// in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine6Test() {
+ duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
iaid_ = 42;
// instantiate cfg_mgr
@@ -59,12 +77,15 @@ public:
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1::10"),
IOAddress("2001:db8:1::20")));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
factory_.create("type=memfile");
}
+ /// @brief checks if Lease6 matches expected configuration
+ ///
+ /// @param lease lease to be checked
void checkLease6(const Lease6Ptr& lease) {
// that is belongs to the right subnet
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
@@ -84,20 +105,80 @@ public:
// @todo: check cltt
}
- ~AllocEngineTest() {
+ ~AllocEngine6Test() {
factory_.destroy();
}
- DuidPtr duid_;
- uint32_t iaid_;
- Subnet6Ptr subnet_;
- Pool6Ptr pool_;
- LeaseMgrFactory factory_;
+ DuidPtr duid_; ///< client-identifier (value used in tests)
+ uint32_t iaid_; ///< IA identifier (value used in tests)
+ Subnet6Ptr subnet_; ///< subnet6 (used in tests)
+ Pool6Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+ /// @brief Default constructor
+ ///
+ /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+ /// used in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine4Test() {
+ clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+ static uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+ // Let's use odd hardware type to check if there is no Ethernet
+ // hardcoded anywhere.
+ hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+ // instantiate cfg_mgr
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.109")));
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ factory_.create("type=memfile");
+ }
+
+ /// @brief checks if Lease4 matches expected configuration
+ ///
+ /// @param lease lease to be checked
+ void checkLease4(const Lease4Ptr& lease) {
+ // that is belongs to the right subnet
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(lease->addr_));
+
+ // that it have proper parameters
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+ EXPECT_EQ(subnet_->getT1(), lease->t1_);
+ EXPECT_EQ(subnet_->getT2(), lease->t2_);
+ EXPECT_TRUE(false == lease->fqdn_fwd_);
+ EXPECT_TRUE(false == lease->fqdn_rev_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
+ // @todo: check cltt
+ }
+
+ ~AllocEngine4Test() {
+ factory_.destroy();
+ }
+
+ ClientIdPtr clientid_; ///< client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< hardware address (value used in tests)
+ Subnet4Ptr subnet_; ///< subnet4 (used in tests)
+ Pool4Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
};
// This test checks if the Allocation Engine can be instantiated and that it
// parses parameters string properly.
-TEST_F(AllocEngineTest, constructor) {
+TEST_F(AllocEngine6Test, constructor) {
boost::scoped_ptr<AllocEngine> x;
// Hashed and random allocators are not supported yet
@@ -107,28 +188,8 @@ TEST_F(AllocEngineTest, constructor) {
ASSERT_NO_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
}
-/// @todo: This method is taken from mysql_lease_mgr_utilities.cc from ticket
-/// #2342. Get rid of one instance once the code is merged
-void
-detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
- EXPECT_EQ(first->type_, second->type_);
-
- // Compare address strings - odd things happen when they are different
- // as the EXPECT_EQ appears to call the operator uint32_t() function,
- // which causes an exception to be thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
- EXPECT_EQ(first->prefixlen_, second->prefixlen_);
- EXPECT_EQ(first->iaid_, second->iaid_);
- EXPECT_TRUE(*first->duid_ == *second->duid_);
- EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
- EXPECT_EQ(first->valid_lft_, second->valid_lft_);
- EXPECT_EQ(first->cltt_, second->cltt_);
- EXPECT_EQ(first->subnet_id_, second->subnet_id_);
-}
-
-
// This test checks if the simple allocation can succeed
-TEST_F(AllocEngineTest, simpleAlloc) {
+TEST_F(AllocEngine6Test, simpleAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -147,11 +208,11 @@ TEST_F(AllocEngineTest, simpleAlloc) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the fake allocation (for SOLICIT) can succeed
-TEST_F(AllocEngineTest, fakeAlloc) {
+TEST_F(AllocEngine6Test, fakeAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -172,7 +233,7 @@ TEST_F(AllocEngineTest, fakeAlloc) {
// This test checks if the allocation with a hint that is valid (in range,
// in pool and free) can succeed
-TEST_F(AllocEngineTest, allocWithValidHint) {
+TEST_F(AllocEngine6Test, allocWithValidHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -195,20 +256,21 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the allocation with a hint that is in range,
// in pool, but is currently used) can succeed
-TEST_F(AllocEngineTest, allocWithUsedHint) {
+TEST_F(AllocEngine6Test, allocWithUsedHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
// let's create a lease and put it in the LeaseMgr
DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+ time_t now = time(NULL);
Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
- duid2, 1, 2, 3, 4, 5, subnet_->getID()));
+ duid2, 1, 2, 3, 4, now, subnet_->getID()));
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
// another client comes in and request an address that is in pool, but
@@ -234,12 +296,12 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
-TEST_F(AllocEngineTest, allocBogusHint) {
+TEST_F(AllocEngine6Test, allocBogusHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -264,12 +326,12 @@ TEST_F(AllocEngineTest, allocBogusHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test verifies that the allocator picks addresses that belong to the
// pool
-TEST_F(AllocEngineTest, IterativeAllocator) {
+TEST_F(AllocEngine6Test, IterativeAllocator) {
boost::scoped_ptr<NakedAllocEngine::Allocator>
alloc(new NakedAllocEngine::IterativeAllocator());
@@ -283,7 +345,7 @@ TEST_F(AllocEngineTest, IterativeAllocator) {
// This test verifies that the iterative allocator really walks over all addresses
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
-TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
+TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
@@ -296,14 +358,14 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
IOAddress(max.str())));
// cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
- subnet_->addPool6(pool);
+ subnet_->addPool(pool);
}
int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
// there are 8 extra pools with 9 addresses in each.
// Let's keep picked addresses here and check their uniqueness.
- std::map<IOAddress, int> generated_addrs;
+ std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
@@ -316,7 +378,381 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
if (generated_addrs.find(candidate) == generated_addrs.end()) {
// we haven't had this
- generated_addrs[candidate] = 0;
+ generated_addrs.insert(candidate);
+ } else {
+ // we have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // we have exactly the number of address in all pools
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if ( cnt>total ) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+
+ delete alloc;
+}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine6Test, smallPool6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ false);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
+
+ // do all checks on the lease
+ checkLease6(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine6Test, outOfAddresses6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ EXPECT_THROW(engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),false),
+ AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
+TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // CASE 1: Asking for any address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease6(lease);
+
+ // CASE 2: Asking specifically for this address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress(addr.toText()),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Let's create an expired lease
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ const SubnetID other_subnetid = 999;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, other_subnetid, 0));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress(addr.toText()), false);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// --- IPv4 ---
+
+// This test checks if the simple IPv4 allocation can succeed
+TEST_F(AllocEngine4Test, simpleAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for DISCOVER) can succeed
+TEST_F(AllocEngine4Test, fakeAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is NOT in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_FALSE(from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine4Test, allocWithValidHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.105"),
+ false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should get what we asked for
+ EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used) can succeed
+TEST_F(AllocEngine4Test, allocWithUsedHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ clientid2, sizeof(clientid2), 1, 2, 3, now, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Another client comes in and request an address that is in pool, but
+ // unfortunately it is used already. The same address must not be allocated
+ // twice.
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.106"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Allocated address must be different
+ EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine4Test, allocBogusHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Client would like to get a 3000::abc lease, which does not belong to any
+ // supported lease. Allocation engine should ignore it and carry on
+ // with the normal allocation
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("10.1.1.1"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(AllocEngine4Test, IterativeAllocator) {
+ boost::scoped_ptr<NakedAllocEngine::Allocator>
+ alloc(new NakedAllocEngine::IterativeAllocator());
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_,
+ IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+ }
+}
+
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
+ NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+ // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "192.0.2." << i * 10 + 1;
+ max << "192.0.2." << i * 10 + 9;
+
+ Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+ IOAddress(max.str())));
+ // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+ subnet_->addPool(pool);
+ }
+
+ int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined subnets.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this
+ generated_addrs.insert(candidate);
} else {
// we have seen this address before. That should mean that we
// iterated over all addresses.
@@ -337,4 +773,193 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
delete alloc;
}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine4Test, smallPool4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine4Test, outOfAddresses4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2),
+ 501, 502, 503, now, subnet_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ EXPECT_THROW(engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),false),
+ AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in DISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease4(lease);
+
+ // CASE 2: Asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.105");
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress(addr.toText()), false);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease is really renewed when renewLease4 method is
+// called
+TEST_F(AllocEngine4Test, renewLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.102");
+ const uint32_t old_lifetime = 100;
+ const uint32_t old_t1 = 50;
+ const uint32_t old_t2 = 75;
+ const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
+
+ // Just a different hw/client-id for the second client
+ const uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2,
+ sizeof(hwaddr2), old_lifetime, old_t1, old_t2,
+ old_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
+ // renew it.
+ ASSERT_FALSE(lease->expired());
+ lease = engine->renewLease4(subnet_, clientid_, hwaddr_, lease, false);
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
+ checkLease4(lease);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 7b23bcf..d023a24 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_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
@@ -39,13 +39,180 @@ namespace {
class CfgMgrTest : public ::testing::Test {
public:
CfgMgrTest() {
+ // make sure we start with a clean configuration
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().deleteSubnets6();
}
~CfgMgrTest() {
+ // clean up after the test
+ CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
}
};
+// This test verifies that multiple option definitions can be added
+// under different option spaces.
+TEST_F(CfgMgrTest, getOptionDefs) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs1 = cfg_mgr.getOptionDefs("isc");
+ ASSERT_TRUE(option_defs1);
+ ASSERT_EQ(10, option_defs1->size());
+
+ // Iterate over all option definitions and check that they have
+ // valid codes. Also, their order should be the same as they
+ // were added (codes 100-109).
+ uint16_t code = 100;
+ for (OptionDefContainer::const_iterator it = option_defs1->begin();
+ it != option_defs1->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs2 = cfg_mgr.getOptionDefs("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(10, option_defs2->size());
+
+ // Check that the option codes are valid.
+ code = 105;
+ for (OptionDefContainer::const_iterator it = option_defs2->begin();
+ it != option_defs2->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Let's make one more check that the empty set is returned when
+ // invalid option space is used.
+ OptionDefContainerPtr option_defs3 = cfg_mgr.getOptionDefs("non-existing");
+ ASSERT_TRUE(option_defs3);
+ EXPECT_TRUE(option_defs3->empty());
+}
+
+// This test verifies that single option definition is correctly
+// returned with getOptionDef function.
+TEST_F(CfgMgrTest, getOptionDef) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Try to get option definitions one by one using all codes
+ // that we expect to be there.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("isc", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that the option codes are valid.
+ for (uint16_t code = 105; code < 115; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("abcde", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-other-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that an option definition can be added to the standard
+ // (dhcp4 and dhcp6) option spaces when the option code is not
+ // reserved by the standard option.
+ OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def6, "dhcp6"));
+
+ OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def4, "dhcp4"));
+
+ // Try to query the option definition from an non-existing
+ // option space and expect NULL pointer.
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("non-existing", 56);
+ EXPECT_FALSE(def);
+
+ // Try to get the non-existing option definition from an
+ // existing option space.
+ EXPECT_FALSE(cfg_mgr.getOptionDef("isc", 56));
+
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST_F(CfgMgrTest, addOptionDefNegative) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // The option code 65 is reserved for standard options either in
+ // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
+ // option space fails.
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
+
+ // Try reserved option space names.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+ // Try empty option space name.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
+ // Try NULL option definition.
+ ASSERT_THROW(cfg_mgr.addOptionDef(OptionDefinitionPtr(), "isc"),
+ isc::dhcp::MalformedOptionDefinition);
+ // Try adding option definition twice and make sure that it
+ // fails on the second attempt.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ EXPECT_THROW(cfg_mgr.addOptionDef(def, "isc"), DuplicateOptionDefinition);
+}
// This test verifies if the configuration manager is able to hold and return
// valid leases
@@ -56,8 +223,8 @@ TEST_F(CfgMgrTest, subnet4) {
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
cfg_mgr.addSubnet4(subnet1);
@@ -74,12 +241,17 @@ TEST_F(CfgMgrTest, subnet4) {
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
// Try to find an address that does not belong to any subnet
- EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets4();
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
}
// This test verifies if the configuration manager is able to hold and return
// valid leases
-
TEST_F(CfgMgrTest, subnet6) {
CfgMgr& cfg_mgr = CfgMgr::instance();
@@ -87,8 +259,8 @@ TEST_F(CfgMgrTest, subnet6) {
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
cfg_mgr.addSubnet6(subnet1);
@@ -104,12 +276,79 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ // Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
+}
+
+// This test verifies that new DHCPv4 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces4();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decode if a duplicate vendor space is allowed.
+}
+
+// This test verifies that new DHCPv6 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces6();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decide if a duplicate vendor space is allowed.
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index e343c44..9924476 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -37,16 +38,136 @@ public:
}
};
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
+// This test checks that a database access string can be parsed correctly.
TEST_F(LeaseMgrFactoryTest, parse) {
- std::map<std::string, std::string> parameters = LeaseMgrFactory::parse(
- "param1=value1 param2=value2 param3=value3");
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea somethingelse= type=mysql");
- EXPECT_EQ("value1", parameters["param1"]);
- EXPECT_EQ("value2", parameters["param2"]);
- EXPECT_TRUE(parameters.find("type") == parameters.end());
+ EXPECT_EQ(5, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+ EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST_F(LeaseMgrFactoryTest, parseInvalid) {
+
+ // No tokens in the string, so we expect no parameters
+ std::string invalid = "";
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(0, parameters.size());
+
+ // With spaces, there are some tokens so we expect invalid parameter
+ // as there are no equals signs.
+ invalid = " \t ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ invalid = " noequalshere ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ // A single "=" is valid string, but is placed here as the result is
+ // expected to be nothing.
+ invalid = "=";
+ parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST_F(LeaseMgrFactoryTest, redactAccessString) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // ... and again to check that the position of the empty password in the
+ // string does not matter.
+ parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begion with.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql");
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
}
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index a812811..8d8c7f8 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -258,7 +258,7 @@ TEST(Lease4, Lease4Constructor) {
// ...and a time
const time_t current_time = time(NULL);
- // Other random constants.
+ // Other random constants.
const uint32_t SUBNET_ID = 42;
const uint32_t VALID_LIFETIME = 500;
@@ -272,7 +272,7 @@ TEST(Lease4, Lease4Constructor) {
// Create the lease
Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
SUBNET_ID);
EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
@@ -312,10 +312,10 @@ TEST(Lease4, OperatorEquals) {
// Check when the leases are equal.
Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
EXPECT_TRUE(lease1 == lease2);
EXPECT_FALSE(lease1 != lease2);
@@ -605,4 +605,29 @@ TEST(Lease6, OperatorEquals) {
EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
EXPECT_FALSE(lease1 != lease2); // ... leases equal
}
+
+// Checks if lease expiration is calculated properly
+TEST(Lease6, Lease6Expired) {
+ const IOAddress addr("2001:db8:1::456");
+ const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ const uint32_t iaid = 7; // just a number
+ const SubnetID subnet_id = 8; // just another number
+ Lease6 lease(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+
+ // case 1: a second before expiration
+ lease.cltt_ = time(NULL) - 100;
+ lease.valid_lft_ = 101;
+ EXPECT_FALSE(lease.expired());
+
+ // case 2: the lease will expire after this second is concluded
+ lease.cltt_ = time(NULL) - 101;
+ EXPECT_FALSE(lease.expired());
+
+ // case 3: the lease is expired
+ lease.cltt_ = time(NULL) - 102;
+ EXPECT_TRUE(lease.expired());
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 746ef00..ddb2645 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/tests/test_utils.h>
#include <gtest/gtest.h>
@@ -29,6 +30,7 @@
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::dhcp::test;
using namespace std;
namespace {
@@ -537,48 +539,6 @@ public:
vector<IOAddress> ioaddress6_; ///< IOAddress forms of IPv6 addresses
};
-///@{
-/// @brief Test Utilities
-///
-/// The follow are a set of functions used during the tests.
-
-/// @brief Compare two Lease4 structures for equality
-void
-detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
- // Compare address strings. Comparison of address objects is not used, as
- // odd things happen when they are different: the EXPECT_EQ macro appears to
- // call the operator uint32_t() function, which causes an exception to be
- // thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
- EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
- EXPECT_TRUE(*first->client_id_ == *second->client_id_);
- EXPECT_EQ(first->valid_lft_, second->valid_lft_);
- EXPECT_EQ(first->cltt_, second->cltt_);
- EXPECT_EQ(first->subnet_id_, second->subnet_id_);
-}
-
-/// @brief Compare two Lease6 structures for equality
-void
-detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
- EXPECT_EQ(first->type_, second->type_);
-
- // Compare address strings. Comparison of address objects is not used, as
- // odd things happen when they are different: the EXPECT_EQ macro appears to
- // call the operator uint32_t() function, which causes an exception to be
- // thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
- EXPECT_EQ(first->prefixlen_, second->prefixlen_);
- EXPECT_EQ(first->iaid_, second->iaid_);
- EXPECT_TRUE(*first->duid_ == *second->duid_);
- EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
- EXPECT_EQ(first->valid_lft_, second->valid_lft_);
- EXPECT_EQ(first->cltt_, second->cltt_);
- EXPECT_EQ(first->subnet_id_, second->subnet_id_);
-}
-
-///@}
-
-
/// @brief Check that database can be opened
///
/// This test checks if the MySqlLeaseMgr can be instantiated. This happens
@@ -810,7 +770,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
}
// Get the leases matching the hardware address of lease 1
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
// Should be three leases, matching leases[1], [3] and [5].
ASSERT_EQ(3, returned.size());
@@ -827,13 +789,15 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
EXPECT_EQ(straddress4_[5], addresses[2]);
// Repeat test with just one expected match
- returned = lmptr_->getLease4(leases[2]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(1, returned.size());
detailCompareLease(leases[2], *returned.begin());
// Check that an empty vector is valid
EXPECT_TRUE(leases[7]->hwaddr_.empty());
- returned = lmptr_->getLease4(leases[7]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
@@ -856,7 +820,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
@@ -871,7 +836,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
// be any indication in the C API.
leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(0, returned.size());
}
@@ -888,27 +854,31 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Get the leases matching the hardware address of lease 1 and
// subnet ID of lease 1. Result should be a single lease - lease 1.
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
// Try for a match to the hardware address of lease 1 and the wrong
// subnet ID.
- returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
// Try for a match to the subnet ID of lease 1 (and lease 4) but
// the wrong hardware address.
vector<uint8_t> invalid_hwaddr(15, 0x77);
- returned = lmptr_->getLease4(invalid_hwaddr,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_);
EXPECT_FALSE(returned);
// Try for a match to an unknown hardware address and an unknown
// subnet ID.
- returned = lmptr_->getLease4(invalid_hwaddr,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
@@ -920,7 +890,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
leases[1]->addr_ = leases[2]->addr_;
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- EXPECT_THROW(returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_),
isc::dhcp::MultipleRecords);
@@ -945,7 +916,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
@@ -959,7 +931,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
// be any indication in the C API.
leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
EXPECT_FALSE(returned);
}
diff --git a/src/lib/dhcpsrv/tests/option_space_unittest.cc b/src/lib/dhcpsrv/tests/option_space_unittest.cc
new file mode 100644
index 0000000..f8d75c8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/option_space_unittest.cc
@@ -0,0 +1,150 @@
+// 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
+// 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 <dhcpsrv/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+ // Create some option space.
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Create another object with different values
+ // to check that the values will change.
+ OptionSpace space2("abc", false);
+ EXPECT_EQ("abc", space2.getName());
+ EXPECT_FALSE(space2.isVendorSpace());
+
+ // Verify that constructor throws exception if invalid
+ // option space name is provided.
+ EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overriden.
+TEST(OptionSpaceTest, setVendorSpace) {
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Override the vendor space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+ // Positive test scenarios: letters, digits, dashes, underscores
+ // lower/upper case allowed.
+ EXPECT_TRUE(OptionSpace::validateName("abc"));
+ EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+ EXPECT_TRUE(OptionSpace::validateName("1234"));
+ EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+ // Negative test scenarions: empty strings, dots, spaces are not
+ // allowed
+ EXPECT_FALSE(OptionSpace::validateName(""));
+ EXPECT_FALSE(OptionSpace::validateName(" "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc "));
+ EXPECT_FALSE(OptionSpace::validateName("isc "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ EXPECT_FALSE(OptionSpace::validateName("-isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc-"));
+ EXPECT_FALSE(OptionSpace::validateName("_isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+ // Test other special characters
+ const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+ '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+ for (int i = 0; i < sizeof(specials); ++i) {
+ std::ostringstream stream;
+ // Concatenate valid option space name: "abc" with an invalid character.
+ // That way we get option space names like: "abc!", "abc$" etc. It is
+ // expected that the validating function fails form them.
+ stream << "abc" << specials[i];
+ EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+ << "Test failed for special character '" << specials[i] << "'.";
+ }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+ // Create some option space and do not specify enterprise number.
+ // In such case the vendor space flag is expected to be
+ // set to false.
+ OptionSpace6 space1("abcd");
+ EXPECT_EQ("abcd", space1.getName());
+ EXPECT_FALSE(space1.isVendorSpace());
+
+ // Create an option space and specify an enterprise number. In this
+ // case the vendor space flag is expected to be set to true and the
+ // enterprise number should be set to a desired value.
+ OptionSpace6 space2("abcd", 2145);
+ EXPECT_EQ("abcd", space2.getName());
+ EXPECT_TRUE(space2.isVendorSpace());
+ EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+ // Verify that constructors throw an exception when invalid option
+ // space name has been specified.
+ EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+ EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+ OptionSpace6 space("isc");
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_FALSE(space.isVendorSpace());
+
+ // Mark it vendor option space and set enterprise id.
+ space.setVendorSpace(1234);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+ // Override the enterprise number to make sure and make sure that
+ // the new number is returned by the object.
+ space.setVendorSpace(2345);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+ // Clear the vendor option space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9ebef9c..689af87 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_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
@@ -61,28 +61,28 @@ TEST(Subnet4Test, Pool4InSubnet4) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
- Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
- Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
- Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+ PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool4Ptr mypool = subnet->getPool4();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool4(pool2);
- subnet->addPool4(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool4();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+ mypool = subnet->getPool(IOAddress("192.1.2.195"));
EXPECT_EQ(mypool, pool3);
@@ -94,16 +94,16 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
- EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
- EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
}
TEST(Subnet4Test, addInvalidOption) {
@@ -115,13 +115,15 @@ TEST(Subnet4Test, addInvalidOption) {
// Create option with invalid universe (V6 instead of V4).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp4"),
+ isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+ isc::BadValue);
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -130,7 +132,7 @@ TEST(Subnet4Test, inRangeinPool) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
@@ -205,28 +207,28 @@ TEST(Subnet6Test, Pool6InSubnet6) {
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
- Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
+ PoolPtr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ PoolPtr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
+ PoolPtr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool6Ptr mypool = subnet->getPool6();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool6(pool2);
- subnet->addPool6(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool6();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
+ mypool = subnet->getPool(IOAddress("2001:db8:1:3::dead:beef"));
EXPECT_EQ(mypool, pool3);
}
@@ -237,21 +239,21 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::"), 48));
- EXPECT_THROW(subnet->addPool6(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
- EXPECT_THROW(subnet->addPool6(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
- EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+ EXPECT_THROW(subnet->addPool(pool4), BadValue);
}
TEST(Subnet6Test, addOptions) {
@@ -261,26 +263,59 @@ TEST(Subnet6Test, addOptions) {
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "isc"));
}
// Get options from the Subnet and check if all 10 are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(10, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
- // Validate codes of added options.
+ // Validate codes of options added to dhcp6 option space.
uint16_t expected_code = 100;
- for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
- option_desc != options.end(); ++option_desc) {
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
ASSERT_TRUE(option_desc->option);
EXPECT_EQ(expected_code, option_desc->option->getType());
++expected_code;
}
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getOptionDescriptors("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ // Delete options from all spaces.
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ // Make sure that all options have been removed.
+ options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
}
TEST(Subnet6Test, addNonUniqueOptions) {
@@ -292,19 +327,19 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
}
}
// Sanity check that all options are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(20, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(20, options->size());
// Use container index #1 to get the options by their codes.
- Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
- // For each code we should get two instances of options.
+ // For each code we should get two instances of options->
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(code);
@@ -329,8 +364,8 @@ TEST(Subnet6Test, addNonUniqueOptions) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
}
TEST(Subnet6Test, addInvalidOption) {
@@ -342,13 +377,13 @@ TEST(Subnet6Test, addInvalidOption) {
// Create option with invalid universe (V4 instead of V6).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp6"), isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
}
TEST(Subnet6Test, addPersistentOption) {
@@ -367,24 +402,24 @@ TEST(Subnet6Test, addPersistentOption) {
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
- ASSERT_NO_THROW(subnet->addOption(option, persistent));
+ ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
}
// Get added options from the subnet.
- Subnet::OptionContainer options = subnet->getOptions();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
- // options.get<2> returns reference to container index #2. This
+ // options->get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
- Subnet::OptionContainerPersistIndex& idx = options.get<2>();
+ Subnet::OptionContainerPersistIndex& idx = options->get<2>();
- // Get all persistent options.
+ // Get all persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
idx.equal_range(true);
// 3 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
- // Get all non-persistent options.
+ // Get all non-persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
idx.equal_range(false);
@@ -393,8 +428,33 @@ TEST(Subnet6Test, addPersistentOption) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
+}
+
+TEST(Subnet6Test, getOptionDescriptor) {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 56, 1, 2, 3, 4));
+
+ // Add 10 options to a "dhcp6" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Check that we can get each added option descriptor using
+ // individually.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+ // Returned descriptor should contain NULL option ptr.
+ EXPECT_FALSE(desc.option);
+ // Now, try the valid option space.
+ desc = subnet->getOptionDescriptor("dhcp6", code);
+ // Test that the option code matches the expected code.
+ ASSERT_TRUE(desc.option);
+ EXPECT_EQ(code, desc.option->getType());
+ }
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -404,7 +464,7 @@ TEST(Subnet6Test, inRangeinPool) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
IOAddress("2001:db8::20")));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
new file mode 100644
index 0000000..3c69dbe
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "test_utils.h"
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+}
+
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
+ EXPECT_EQ(first->type_, second->type_);
+
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_EQ(first->prefixlen_, second->prefixlen_);
+ EXPECT_EQ(first->iaid_, second->iaid_);
+ ASSERT_TRUE(first->duid_);
+ ASSERT_TRUE(second->duid_);
+ EXPECT_TRUE(*first->duid_ == *second->duid_);
+ EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+}
+
+};
+};
+};
diff --git a/src/lib/dhcpsrv/tests/test_utils.h b/src/lib/dhcpsrv/tests/test_utils.h
new file mode 100644
index 0000000..46df9fc
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_utils.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBDHCPSRV_TEST_UTILS_H
+#define LIBDHCPSRV_TEST_UTILS_H
+
+#include <dhcpsrv/lease_mgr.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// @brief performs details comparison between two IPv6 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second);
+
+// @brief performs details comparison between two IPv4 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second);
+
+
+};
+};
+};
+
+#endif
diff --git a/src/lib/dhcpsrv/utils.h b/src/lib/dhcpsrv/utils.h
new file mode 100644
index 0000000..26d98ce
--- /dev/null
+++ b/src/lib/dhcpsrv/utils.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_UTILS_H
+#define DHCPSRV_UTILS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCPSRV_UTILS_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 6d8d162..286bd8c 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -23,6 +23,7 @@ EXTRA_DIST += rdata/generic/cname_5.cc
EXTRA_DIST += rdata/generic/cname_5.h
EXTRA_DIST += rdata/generic/detail/char_string.cc
EXTRA_DIST += rdata/generic/detail/char_string.h
+EXTRA_DIST += rdata/generic/detail/lexer_util.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -95,6 +96,7 @@ lib_LTLIBRARIES = libb10-dns++.la
libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
libb10_dns___la_SOURCES =
+libb10_dns___la_SOURCES += dns_fwd.h
libb10_dns___la_SOURCES += edns.h edns.cc
libb10_dns___la_SOURCES += exceptions.h exceptions.cc
libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
@@ -124,11 +126,11 @@ libb10_dns___la_SOURCES += tsig.h tsig.cc
libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
-libb10_dns___la_SOURCES += character_string.h character_string.cc
libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
libb10_dns___la_SOURCES += master_loader.h
libb10_dns___la_SOURCES += rrset_collection_base.h
libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
@@ -158,6 +160,7 @@ libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
libdns___include_HEADERS = \
edns.h \
exceptions.h \
+ dns_fwd.h \
labelsequence.h \
message.h \
masterload.h \
@@ -175,7 +178,8 @@ libdns___include_HEADERS = \
rrset_collection_base.h \
rrset_collection.h \
rrttl.h \
- tsigkey.h
+ tsigkey.h \
+ zone_checker.h
# Purposely not installing these headers:
# name_internal.h: used only internally, and not actually DNS specific
# rdata/*/detail/*.h: these are internal use only
diff --git a/src/lib/dns/character_string.cc b/src/lib/dns/character_string.cc
deleted file mode 100644
index 8b31948..0000000
--- a/src/lib/dns/character_string.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "character_string.h"
-#include "rdata.h"
-
-using namespace std;
-using namespace isc::dns::rdata;
-
-namespace isc {
-namespace dns {
-
-namespace {
-bool isDigit(char c) {
- return (('0' <= c) && (c <= '9'));
-}
-}
-
-std::string
-characterstr::getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool* quoted)
-{
- string result;
-
- // If the input string only contains white-spaces, it is an invalid
- // <character-string>
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText, "Invalid text format, \
- <character-string> field is missing.");
- }
-
- // Whether the <character-string> is separated with double quotes (")
- bool quotes_separated = (*input_iterator == '"');
- // Whether the quotes are pared if the string is quotes separated
- bool quotes_paired = false;
-
- if (quotes_separated) {
- ++input_iterator;
- }
-
- while(input_iterator < input_str.end()){
- // Escaped characters processing
- if (*input_iterator == '\\') {
- if (input_iterator + 1 == input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- if (isDigit(*(input_iterator + 1))) {
- // \DDD where each D is a digit. It its the octet
- // corresponding to the decimal number described by DDD
- if (input_iterator + 3 >= input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- int n = 0;
- ++input_iterator;
- for (int i = 0; i < 3; ++i) {
- if (isDigit(*input_iterator)) {
- n = n*10 + (*input_iterator - '0');
- ++input_iterator;
- } else {
- isc_throw(InvalidRdataText, "Illegal decimal \
- escaping series");
- }
- }
- if (n > 255) {
- isc_throw(InvalidRdataText, "Illegal octet \
- number");
- }
- result.push_back(n);
- continue;
- }
- } else {
- ++input_iterator;
- result.push_back(*input_iterator);
- ++input_iterator;
- continue;
- }
- }
- }
-
- if (quotes_separated) {
- // If the <character-string> is seperated with quotes symbol and
- // another quotes symbol is encountered, it is the end of the
- // <character-string>
- if (*input_iterator == '"') {
- quotes_paired = true;
- ++input_iterator;
- // Reach the end of character string
- break;
- }
- } else if (*input_iterator == ' ') {
- // If the <character-string> is not seperated with quotes symbol,
- // it is seperated with <space> char
- break;
- }
-
- result.push_back(*input_iterator);
-
- ++input_iterator;
- }
-
- if (result.size() > MAX_CHARSTRING_LEN) {
- isc_throw(CharStringTooLong, "<character-string> is too long");
- }
-
- if (quotes_separated && !quotes_paired) {
- isc_throw(InvalidRdataText, "The quotes are not paired");
- }
-
- if (quoted != NULL) {
- *quoted = quotes_separated;
- }
-
- return (result);
-}
-
-std::string
-characterstr::getNextCharacterString(util::InputBuffer& buffer, size_t len) {
- uint8_t str_len = buffer.readUint8();
-
- size_t pos = buffer.getPosition();
- if (len - pos < str_len) {
- isc_throw(InvalidRdataLength, "Invalid string length");
- }
-
- uint8_t buf[MAX_CHARSTRING_LEN];
- buffer.readData(buf, str_len);
- return (string(buf, buf + str_len));
-}
-
-} // end of namespace dns
-} // end of namespace isc
diff --git a/src/lib/dns/character_string.h b/src/lib/dns/character_string.h
deleted file mode 100644
index 0bfa38b..0000000
--- a/src/lib/dns/character_string.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CHARACTER_STRING_H
-#define CHARACTER_STRING_H
-
-#include <string>
-#include <exceptions/exceptions.h>
-#include <util/buffer.h>
-
-namespace isc {
-namespace dns {
-
-// \brief Some utility functions to extract <character-string> from string
-// or InputBuffer
-//
-// <character-string> is expressed in one or two ways: as a contiguous set
-// of characters without interior spaces, or as a string beginning with a "
-// and ending with a ". Inside a " delimited string any character can
-// occur, except for a " itself, which must be quoted using \ (back slash).
-// Ref. RFC1035
-
-
-namespace characterstr {
- /// Get a <character-string> from a string
- ///
- /// \param input_str The input string
- /// \param input_iterator The iterator from which to start extracting,
- /// the iterator will be updated to new position after the function
- /// is returned
- /// \param quoted If not \c NULL, returns \c true at this address if
- /// the string is quoted, \cfalse otherwise
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool* quoted = NULL);
-
- /// Get a <character-string> from a input buffer
- ///
- /// \param buffer The input buffer
- /// \param len The input buffer total length
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(util::InputBuffer& buffer, size_t len);
-
-} // namespace characterstr
-} // namespace dns
-} // namespace isc
-
-#endif // CHARACTER_STRING_H
diff --git a/src/lib/dns/dns_fwd.h b/src/lib/dns/dns_fwd.h
new file mode 100644
index 0000000..df71388
--- /dev/null
+++ b/src/lib/dns/dns_fwd.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_FWD_H
+#define DNS_FWD_H 1
+
+/// \file dns_fwd.h
+/// \brief Forward declarations for definitions of libdns++
+///
+/// This file provides a set of forward declarations for definitions commonly
+/// used in libdns++ to help minimize dependency when actual the definition
+/// is not necessary.
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MasterLoader;
+class MasterLoaderCallbacks;
+class Message;
+class AbstractMessageRenderer;
+class MessageRenderer;
+class NSEC3Hash;
+class NSEC3HashCreator;
+class Opcode;
+class Question;
+class Rcode;
+namespace rdata {
+class Rdata;
+}
+class RRCollator;
+class RRClass;
+class RRType;
+class RRTTL;
+class AbstractRRset;
+class RdataIterator;
+class RRsetCollectionBase;
+class RRsetCollection;
+class Serial;
+class TSIGContext;
+class TSIGError;
+class TSIGKey;
+class TSIGKeyRing;
+class TSIGRecord;
+
+} // namespace dns
+} // namespace isc
+#endif // DNS_FWD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 2bf9de6..fc63d73 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -32,8 +32,13 @@ import sys
#
# Example:
# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('aaaa', 'in'), ('txt', 'generic'),
- ('spf', 'generic')]
+new_rdata_factory_users = [('aaaa', 'in'),
+ ('hinfo', 'generic'),
+ ('naptr', 'generic'),
+ ('soa', 'generic'),
+ ('spf', 'generic'),
+ ('txt', 'generic')
+ ]
re_typecode = re.compile('([\da-z]+)_(\d+)')
classcode2txt = {}
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index e4ddedc..518bea3 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -18,17 +18,28 @@
#include <dns/master_lexer_inputsource.h>
#include <dns/master_lexer_state.h>
+#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <bitset>
#include <cassert>
+#include <limits>
#include <string>
#include <vector>
namespace isc {
namespace dns {
+// The definition of SOURCE_SIZE_UNKNOWN. Note that we initialize it using
+// a method of another library. Technically, this could trigger a static
+// initialization fiasco. But in this particular usage it's very unlikely
+// to happen because this value is expected to be used only as a return
+// value of a MasterLexer's method, and its constructor needs definitions
+// here.
+const size_t MasterLexer::SOURCE_SIZE_UNKNOWN =
+ std::numeric_limits<size_t>::max();
+
namespace {
typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
} // end unnamed namespace
@@ -172,6 +183,30 @@ MasterLexer::getSourceLine() const {
return (impl_->sources_.back()->getCurrentLine());
}
+size_t
+MasterLexer::getTotalSourceSize() const {
+ size_t total_size = 0;
+ BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+ // If the size of any pushed source is unknown, the total is also
+ // considered unknown.
+ if (src->getSize() == SOURCE_SIZE_UNKNOWN) {
+ return (SOURCE_SIZE_UNKNOWN);
+ }
+
+ total_size += src->getSize();
+ }
+ return (total_size);
+}
+
+size_t
+MasterLexer::getPosition() const {
+ size_t position = 0;
+ BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+ position += src->getPosition();
+ }
+ return (position);
+}
+
const MasterToken&
MasterLexer::getNextToken(Options options) {
if (impl_->source_ == NULL) {
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 1aa4255..9363ff5 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -331,6 +331,16 @@ public:
const MasterToken token_;
};
+ /// \brief Special value for input source size meaning "unknown".
+ ///
+ /// This constant value will be used as a return value of
+ /// \c getTotalSourceSize() when the size of one of the pushed sources
+ /// is unknown. Note that this value itself is a valid integer in the
+ /// range of the type, so there's still a small possibility of
+ /// ambiguity. In practice, however, the value should be sufficiently
+ /// large that should eliminate the possibility.
+ static const size_t SOURCE_SIZE_UNKNOWN;
+
/// \brief Options for getNextToken.
///
/// A compound option, indicating multiple options are set, can be
@@ -387,6 +397,10 @@ public:
/// The caller can explicitly tell \c MasterLexer to stop using the
/// stream by calling the \c popSource() method.
///
+ /// The data in \c input must be complete at the time of this call.
+ /// The behavior of the lexer is undefined if the caller builds or adds
+ /// data in \c input after pushing it.
+ ///
/// \param input An input stream object that produces textual
/// representation of DNS RRs.
void pushSource(std::istream& input);
@@ -443,6 +457,57 @@ public:
/// \return The current line number of the source (see the description)
size_t getSourceLine() const;
+ /// \brief Return the total size of pushed sources.
+ ///
+ /// This method returns the sum of the size of sources that have been
+ /// pushed to the lexer by the time of the call. It would give the
+ /// caller of some hint about the amount of data the lexer is working on.
+ ///
+ /// The size of a normal file is equal to the file size at the time of
+ /// the source is pushed. The size of other type of input stream is
+ /// the size of the data available in the stream at the time of the
+ /// source is pushed.
+ ///
+ /// In some special cases, it's possible that the size of the file or
+ /// stream is unknown. It happens, for example, if the standard input
+ /// is associated with a pipe from the output of another process and it's
+ /// specified as an input source. If the size of some of the pushed
+ /// pushed source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+ ///
+ /// If there is no source pushed in the lexer, it returns 0.
+ ///
+ /// \throw None
+ size_t getTotalSourceSize() const;
+
+ /// \brief Return the position of lexer in the currently pushed sources.
+ ///
+ /// This method returns the position in terms of the number of recognized
+ /// characters from all sources. Roughly speaking, the position in a
+ /// single source is the offset from the beginning of the file or stream
+ /// to the current "read cursor" of the lexer, and the return value of
+ /// this method is the sum of the position in all the pushed sources.
+ ///
+ /// If the lexer reaches the end for each of all the pushed sources,
+ /// the return value should be equal to that of \c getTotalSourceSize().
+ ///
+ /// If there is no source pushed in the lexer, it returns 0.
+ ///
+ /// The return values of this method and \c getTotalSourceSize() would
+ /// give the caller an idea of the progress of the lexer at the time of
+ /// the call. Note, however, that since it's not predictable whether
+ /// more sources will be pushed after the call, the progress determined
+ /// this way may not make much sense; it can only give an informational
+ /// hint of the progress.
+ ///
+ /// Note also that if a source is popped, this method will normally return
+ /// a smaller number by definition (and so will \c getTotalSourceSize()).
+ /// Likewise, the conceptual "read cursor" would move backward after a
+ /// call to \c ungetToken(), in which case this method will return a
+ /// smaller value, too.
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
/// \brief Parse and return another token from the input.
///
/// It reads a bit of the last opened source and produces another token
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
index 03801f6..ec642a2 100644
--- a/src/lib/dns/master_lexer_inputsource.cc
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -15,6 +15,9 @@
#include <dns/master_lexer_inputsource.h>
#include <dns/master_lexer.h>
+#include <istream>
+#include <iostream>
+#include <cassert>
#include <cerrno>
#include <cstring>
@@ -31,6 +34,37 @@ createStreamName(const std::istream& input_stream) {
return (ss.str());
}
+size_t
+getStreamSize(std::istream& is) {
+ is.seekg(0, std::ios_base::end);
+ if (is.bad()) {
+ // This means the istream has an integrity error. It doesn't make
+ // sense to continue from this point, so we treat it as a fatal error.
+ isc_throw(InputSource::OpenError,
+ "failed to seek end of input source");
+ } else if (is.fail()) {
+ // This is an error specific to seekg(). There can be several
+ // reasons, but the most likely cause in this context is that the
+ // stream is associated with a special type of file such as a pipe.
+ // In this case, it's more likely that other main operations of
+ // the input source work fine, so we continue with just setting
+ // the stream size to "unknown".
+ is.clear(); // clear this error not to confuse later ops.
+ return (MasterLexer::SOURCE_SIZE_UNKNOWN);
+ }
+ const std::streampos len = is.tellg();
+ if (len == static_cast<std::streampos>(-1)) { // cast for some compilers
+ isc_throw(InputSource::OpenError, "failed to get input size");
+ }
+ is.seekg(0, std::ios::beg);
+ if (is.fail()) {
+ isc_throw(InputSource::OpenError,
+ "failed to seek beginning of input source");
+ }
+ assert(len >= 0);
+ return (len);
+}
+
} // end of unnamed namespace
// Explicit definition of class static constant. The value is given in the
@@ -42,31 +76,44 @@ InputSource::InputSource(std::istream& input_stream) :
line_(1),
saved_line_(line_),
buffer_pos_(0),
+ total_pos_(0),
name_(createStreamName(input_stream)),
- input_(input_stream)
+ input_(input_stream),
+ input_size_(getStreamSize(input_))
{}
-InputSource::InputSource(const char* filename) :
- at_eof_(false),
- line_(1),
- saved_line_(line_),
- buffer_pos_(0),
- name_(filename),
- input_(file_stream_)
-{
+namespace {
+// A helper to initialize InputSource::input_ in the member initialization
+// list.
+std::istream&
+openFileStream(std::ifstream& file_stream, const char* filename) {
errno = 0;
- file_stream_.open(filename);
- if (file_stream_.fail()) {
+ file_stream.open(filename);
+ if (file_stream.fail()) {
std::string error_txt("Error opening the input source file: ");
error_txt += filename;
if (errno != 0) {
error_txt += "; possible cause: ";
error_txt += std::strerror(errno);
}
- isc_throw(OpenError, error_txt);
+ isc_throw(InputSource::OpenError, error_txt);
}
+
+ return (file_stream);
+}
}
+InputSource::InputSource(const char* filename) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ total_pos_(0),
+ name_(filename),
+ input_(openFileStream(file_stream_, filename)),
+ input_size_(getStreamSize(input_))
+{}
+
InputSource::~InputSource()
{
if (file_stream_.is_open()) {
@@ -103,6 +150,7 @@ InputSource::getChar() {
const int c = buffer_[buffer_pos_];
++buffer_pos_;
+ ++total_pos_;
if (c == '\n') {
++line_;
}
@@ -119,6 +167,7 @@ InputSource::ungetChar() {
"Cannot skip before the start of buffer");
} else {
--buffer_pos_;
+ --total_pos_;
if (buffer_[buffer_pos_] == '\n') {
--line_;
}
@@ -127,6 +176,8 @@ InputSource::ungetChar() {
void
InputSource::ungetAll() {
+ assert(total_pos_ >= buffer_pos_);
+ total_pos_ -= buffer_pos_;
buffer_pos_ = 0;
line_ = saved_line_;
at_eof_ = false;
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
index 1a4497f..2ad0100 100644
--- a/src/lib/dns/master_lexer_inputsource.h
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -65,12 +65,16 @@ public:
/// \brief Constructor which takes an input stream. The stream is
/// read-from, but it is not closed.
+ ///
+ /// \throws OpenError If the data size of the input stream cannot be
+ /// detected.
explicit InputSource(std::istream& input_stream);
/// \brief Constructor which takes a filename to read from. The
/// associated file stream is managed internally.
///
- /// \throws OpenError when opening the input file fails.
+ /// \throws OpenError when opening the input file fails or the size of
+ /// the file cannot be detected.
explicit InputSource(const char* filename);
/// \brief Destructor
@@ -83,6 +87,34 @@ public:
return (name_);
}
+ /// \brief Returns the size of the input source in bytes.
+ ///
+ /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// See \c MasterLexer::getTotalSourceSize() for the definition of
+ /// the size of sources and for when the size can be unknown.
+ ///
+ /// \throw None
+ size_t getSize() const { return (input_size_); }
+
+ /// \brief Returns the current read position in the input source.
+ ///
+ /// This method returns the position of the character that was last
+ /// retrieved from the source. Unless some characters have been
+ /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal
+ /// to the number of calls to \c getChar() until it reaches the
+ /// END_OF_STREAM. Note that the position of the first character in
+ /// the source is 1. At the point of the last character, the return value
+ /// of this method should be equal to that of \c getSize(), and
+ /// recognizing END_OF_STREAM doesn't increase the position.
+ ///
+ /// If \c ungetChar() or \c ungetAll() is called, the position is
+ /// decreased by the number of "ungotten" characters. So the return
+ /// values may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const { return (total_pos_); }
+
/// \brief Returns if the input source is at end of file.
bool atEOF() const {
return (at_eof_);
@@ -142,10 +174,12 @@ private:
std::vector<char> buffer_;
size_t buffer_pos_;
+ size_t total_pos_;
const std::string name_;
std::ifstream file_stream_;
std::istream& input_;
+ const size_t input_size_;
};
} // namespace master_lexer_internal
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 7ad6c0f..400db43 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -219,28 +219,28 @@ private:
// after the RR class below.
}
- boost::scoped_ptr<RRClass> rrclass;
- try {
- rrclass.reset(new RRClass(rrparam_token.getString()));
+ const MaybeRRClass rrclass =
+ RRClass::createFromText(rrparam_token.getString());
+ if (rrclass) {
+ // FIXME: The following code re-parses the rrparam_token to
+ // make an RRClass instead of using the MaybeRRClass above,
+ // because some old versions of boost::optional (that we
+ // still want to support) have a bug (see trac #2593). This
+ // workaround should be removed at some point in the future.
+ if (RRClass(rrparam_token.getString()) != zone_class_) {
+ isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+ " vs. " << zone_class_);
+ }
rrparam_token = lexer_.getNextToken(MasterToken::STRING);
- } catch (const InvalidRRClass&) {
- // If it's not an rrclass here, use the zone's class.
- rrclass.reset(new RRClass(zone_class_));
}
// If we couldn't parse TTL earlier in the stream (above), try
// again at current location.
- if (!explicit_ttl &&
- setCurrentTTL(rrparam_token.getString())) {
+ if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
explicit_ttl = true;
rrparam_token = lexer_.getNextToken(MasterToken::STRING);
}
- if (*rrclass != zone_class_) {
- isc_throw(InternalException, "Class mismatch: " << *rrclass <<
- "vs. " << zone_class_);
- }
-
// Return the current string token's value as the RRType.
return (RRType(rrparam_token.getString()));
}
@@ -398,7 +398,7 @@ private:
shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
- AddRRCallback add_callback_;
+ const AddRRCallback add_callback_;
boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
// unspecified. If NULL no default
// is known.
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index f9cc18b..e725595 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -100,7 +100,7 @@ public:
/// If the caller of the loader wants to abort, it is possible to throw
/// from the callback, which aborts the load.
void error(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
error_(source_name, source_line, reason);
}
@@ -117,7 +117,7 @@ public:
/// may be false positives), it is possible to throw from inside the
/// callback.
void warning(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
warning_(source_name, source_line, reason);
}
@@ -133,7 +133,7 @@ public:
static MasterLoaderCallbacks getNullCallbacks();
private:
- IssueCallback error_, warning_;
+ const IssueCallback error_, warning_;
};
}
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6dd94b6..a221bfe 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -25,6 +25,9 @@ libb10_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
libb10_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
+libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
+libb10_pydnspp_la_SOURCES += rrset_collection_python.h
+libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -43,6 +46,8 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
EXTRA_DIST = tsigerror_python_inc.cc
EXTRA_DIST += message_python_inc.cc
EXTRA_DIST += nsec3hash_python_inc.cc
+EXTRA_DIST += rrset_collection_python_inc.cc
+EXTRA_DIST += zone_checker_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 6d1bd89..c75c737 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -50,12 +50,16 @@
#include "rrset_python.h"
#include "rrttl_python.h"
#include "rrtype_python.h"
+#include "rrset_collection_python.h"
#include "serial_python.h"
#include "tsigerror_python.h"
#include "tsigkey_python.h"
#include "tsig_python.h"
#include "tsig_rdata_python.h"
#include "tsigrecord_python.h"
+#include "zone_checker_python.h"
+
+#include "zone_checker_python_inc.cc"
using namespace isc::dns;
using namespace isc::dns::python;
@@ -728,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
return (true);
}
+PyMethodDef methods[] = {
+ { "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
+ { NULL, NULL, 0, NULL }
+};
+
PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
@@ -737,13 +746,13 @@ PyModuleDef pydnspp = {
"and OutputBuffer for instance), and others may be necessary, but "
"were not up to now.",
-1,
- NULL,
+ methods,
NULL,
NULL,
NULL,
NULL
};
-}
+} // unnamed namespace
PyMODINIT_FUNC
PyInit_pydnspp(void) {
@@ -864,5 +873,13 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_RRsetCollectionBase(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_RRsetCollection(mod)) {
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
new file mode 100644
index 0000000..df313f7
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -0,0 +1,426 @@
+// 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/name_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrset_collection.h>
+
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "rrset_collection_python_inc.cc"
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+//
+// RRsetCollectionBase
+//
+
+namespace {
+int
+RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
+ PyErr_SetString(PyExc_TypeError,
+ "RRsetCollectionBase cannot be instantiated directly");
+ return (-1);
+}
+
+void
+RRsetCollectionBase_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+ if (self->cppobj == NULL) {
+ PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
+ "derived RRsetCollection class");
+ return (NULL);
+ }
+
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ ConstRRsetPtr found_rrset = self->cppobj->find(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (found_rrset) {
+ return (createRRsetObject(*found_rrset));
+ }
+ Py_RETURN_NONE;
+ }
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollectionBase.find: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollectionBase_methods[] = {
+ { "find", RRsetCollectionBase_find, METH_VARARGS,
+ RRsetCollectionBase_find_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_base_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollectionBase",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollectionBase_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags (it's inheritable)
+ RRsetCollectionBase_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollectionBase_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollectionBase_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollectionBase(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&rrset_collection_base_type) < 0) {
+ return (false);
+ }
+ void* p = &rrset_collection_base_type;
+ if (PyModule_AddObject(mod, "RRsetCollectionBase",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrset_collection_base_type);
+
+ return (true);
+}
+
+//
+// RRsetCollection
+//
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_RRsetCollection, RRsetCollection> RRsetCollectionContainer;
+
+int
+RRsetCollection_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ const char* filename;
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ Py_buffer py_buf;
+
+ if (PyArg_ParseTuple(args, "sO!O!", &filename, &name_type, &po_name,
+ &rrclass_type, &po_rrclass)) {
+ self->cppobj =
+ new RRsetCollection(filename, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "y*O!O!", &py_buf, &name_type,
+ &po_name,&rrclass_type, &po_rrclass)) {
+ PyErr_Clear(); // clear the error for the first ParseTuple
+ const char* const cp = static_cast<const char*>(py_buf.buf);
+ std::istringstream iss(string(cp, cp + py_buf.len));
+ self->cppobj =
+ new RRsetCollection(iss, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "")) {
+ PyErr_Clear(); // clear the error for the second ParseTuple
+ self->cppobj = new RRsetCollection;
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct RRsetCollection object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (-1);
+ }
+
+ // Default error string isn't helpful when it takes multiple combinations
+ // of args. We provide our own.
+ PyErr_SetString(PyExc_TypeError, "Invalid argument(s) to RRsetCollection "
+ "constructor; see pydoc");
+
+ return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollection_addRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_rrset;
+ if (PyArg_ParseTuple(args, "O", &po_rrset)) {
+ if (!PyRRset_Check(po_rrset)) {
+ return (setTypeError(po_rrset, "rrset", "RRset"));
+ }
+ static_cast<RRsetCollection*>(self->cppobj)->addRRset(
+ PyRRset_ToRRsetPtr(po_rrset));
+ Py_RETURN_NONE;
+ }
+ } catch (const InvalidParameter& ex) { // duplicate add
+ PyErr_SetString(PyExc_ValueError, ex.what());
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollection.add_rrset: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+PyObject*
+RRsetCollection_removeRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ const bool result =
+ static_cast<RRsetCollection*>(self->cppobj)->removeRRset(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (...) {}
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollection_methods[] = {
+ { "add_rrset", RRsetCollection_addRRset, METH_VARARGS,
+ RRsetCollection_addRRset_doc },
+ { "remove_rrset", RRsetCollection_removeRRset, METH_VARARGS,
+ RRsetCollection_removeRRset_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollection",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollection_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ RRsetCollection_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollection_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ &rrset_collection_base_type, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollection_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollection(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&rrset_collection_type) < 0) {
+ return (false);
+ }
+ void* p = &rrset_collection_type;
+ if (PyModule_AddObject(mod, "RRsetCollection",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrset_collection_type);
+
+ return (true);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
new file mode 100644
index 0000000..98cb84b
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -0,0 +1,52 @@
+// 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 PYTHON_RRSETCOLLECTION_H
+#define PYTHON_RRSETCOLLECTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRsetCollectionBase;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+// This structure will be commonly used for all derived classes of
+// RRsetCollectionBase. cppobj will point to an instance of the specific
+// derived class.
+class s_RRsetCollection : public PyObject {
+public:
+ s_RRsetCollection() : cppobj(NULL) {}
+ RRsetCollectionBase* cppobj;
+};
+
+// Python type information for RRsetCollectionBase
+extern PyTypeObject rrset_collection_base_type;
+
+// Python type information for dns.RRsetCollection
+extern PyTypeObject rrset_collection_type;
+
+bool initModulePart_RRsetCollectionBase(PyObject* mod);
+bool initModulePart_RRsetCollection(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_RRSETCOLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
new file mode 100644
index 0000000..5c1e532
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -0,0 +1,148 @@
+namespace {
+// Modifications
+// - libdns++ => isc.dns, libdatasrc => isc.datasrc
+// - note about the constructor.
+// - add note about iteration
+const char* const RRsetCollectionBase_doc = "\
+Generic class to represent a set of RRsets.\n\
+\n\
+This is a generic container and the stored set of RRsets does not\n\
+necessarily form a valid zone (e.g. there doesn't necessarily have to\n\
+be an SOA at the \"origin\"). Instead, it will be used to represent a\n\
+single zone for the purpose of zone loading/checking. It provides a\n\
+simple find() method to find an RRset for the given name and type (and\n\
+maybe class) and a way to iterate over all RRsets.\n\
+\n\
+ Note: in the initial version, iteration is not yet supported.\n\
+\n\
+See RRsetCollection for a simple isc.dns implementation. Other modules\n\
+such as isc.datasrc will have another implementation.\n\
+\n\
+This base class cannot be directly instantiated, so no constructor is\n\
+defined.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+// - NULL => None
+// - added types of params
+const char* const RRsetCollectionBase_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, None is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name (isc.dns.Name) The name of the RRset to search for.\n\
+ rrtype (isc.dns.RRType) The type of the RRset to search for.\n\
+ rrclass (isc.dns.RRClass) The class of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, None otherwise.\n\
+";
+
+
+// Modifications
+// - libdns++ => isc.dns
+// - remove STL
+// - MasterLoaderError => IscException
+// - added types of params
+// - input_stream => input, stream => bytes
+const char* const RRsetCollection_doc = "\
+Derived class implementation of RRsetCollectionBase for isc.dns module.\n\
+\n\
+RRsetCollection()\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor creates an empty collection without any data in\n\
+ it. RRsets can be added to the collection with the add_rrset()\n\
+ method.\n\
+\n\
+RRsetCollection(filename, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ The origin and rrclass arguments are required for the zone\n\
+ loading, but RRsetCollection itself does not do any validation,\n\
+ and the collection of RRsets does not have to form a valid zone.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ filename (str) Name of a file containing a collection of RRs in the\n\
+ master file format (which may or may not form a valid\n\
+ zone).\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+RRsetCollection(input_stream, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor is similar to the previous one, but instead of\n\
+ taking a filename to load a zone from, it takes a byte object,\n\
+ representing the zone contents in text.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ input (byte) Textual representation of the zone.\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+";
+
+// Modifications
+// - void => None
+// - InvalidParameter => ValueError
+// - remove ownership related points (doesn't apply here)
+const char* const RRsetCollection_addRRset_doc = "\
+add_rrset(rrset) -> None\n\
+\n\
+Add an RRset to the collection.\n\
+\n\
+Does not do any validation whether rrset belongs to a particular zone\n\
+or not.\n\
+\n\
+It throws a ValueError exception if an rrset with the same\n\
+class, type and name already exists.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+const char* const RRsetCollection_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, NULL is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name The name of the RRset to search for.\n\
+ rrclass The class of the RRset to search for.\n\
+ rrtype The type of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, NULL otherwise.\n\
+";
+
+// Modifications
+// // - true/false => True/False
+const char* const RRsetCollection_removeRRset_doc = "\
+remove_rrset(name, rrclass, rrtype) -> bool\n\
+\n\
+Remove an RRset from the collection.\n\
+\n\
+RRset(s) matching the name, rrclass and rrtype are removed from the\n\
+collection.\n\
+\n\
+True if a matching RRset was deleted, False if no such RRset exists.\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4b0ea9f..de6b010 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,12 +12,14 @@ PYTESTS += rrclass_python_test.py
PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
+PYTESTS += rrset_collection_python_test.py
PYTESTS += serial_python_test.py
PYTESTS += tsig_python_test.py
PYTESTS += tsig_rdata_python_test.py
PYTESTS += tsigerror_python_test.py
PYTESTS += tsigkey_python_test.py
PYTESTS += tsigrecord_python_test.py
+PYTESTS += zone_checker_python_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testutil.py
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
new file mode 100644
index 0000000..2cf286e
--- /dev/null
+++ b/src/lib/dns/python/tests/rrset_collection_python_test.py
@@ -0,0 +1,140 @@
+# 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.
+
+import os
+import unittest
+from pydnspp import *
+
+# This should refer to the testdata diretory for the C++ tests.
+TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
+
+class RRsetCollectionBaseTest(unittest.TestCase):
+ def test_init(self):
+ # direct instantiation of the base class is prohibited.
+ self.assertRaises(TypeError, RRsetCollectionBase)
+
+class RRsetCollectionTest(unittest.TestCase):
+ def test_init_fail(self):
+ # check various failure cases on construction (other normal cases are
+ # covered as part of other tests)
+
+ # bad args
+ self.assertRaises(TypeError, RRsetCollection, 1)
+ self.assertRaises(TypeError, RRsetCollection, # extra arg
+ b'example. 0 A 192.0.2.1',
+ Name('example'), RRClass.IN(), 1)
+ self.assertRaises(TypeError, RRsetCollection, # incorrect order
+ b'example. 0 A 192.0.2.1', RRClass.IN(),
+ Name('example'))
+
+ # constructor will result in C++ exception.
+ self.assertRaises(IscException, RRsetCollection,
+ TESTDATA_DIR + '/no_such_file', Name('example.org'),
+ RRClass.IN())
+
+ def check_find_result(self, rrsets):
+ # Commonly used check pattern
+ found = rrsets.find(Name('www.example.org'), RRClass.IN(), RRType.A())
+ self.assertNotEqual(None, found)
+ self.assertEqual(Name('www.example.org'), found.get_name())
+ self.assertEqual(RRClass.IN(), found.get_class())
+ self.assertEqual(RRType.A(), found.get_type())
+ self.assertEqual('192.0.2.1', found.get_rdata()[0].to_text())
+
+ def test_find(self):
+ # Checking the underlying find() is called as intended, both for
+ # success and failure cases, and with two different constructors.
+ rrsets = RRsetCollection(TESTDATA_DIR + '/example.org',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ rrsets = RRsetCollection(b'www.example.org. 3600 IN A 192.0.2.1',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ def test_find_badargs(self):
+ rrsets = RRsetCollection()
+
+ # Check bad arguments: bad types
+ self.assertRaises(TypeError, rrsets.find, 1, RRClass.IN(), RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), 1)
+ self.assertRaises(TypeError, rrsets.find, Name('example'), RRType.A(),
+ RRClass.IN())
+
+ # Check bad arguments: too many/few arguments
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), RRType.A(), 0)
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN())
+
+ def test_add_remove_rrset(self):
+ name = Name('www.example.org')
+ rrclass = RRClass.IN()
+ rrtype = RRType.A()
+
+ # Create a collection with no RRsets
+ rrsets = RRsetCollection()
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # add the record, then it should be found
+ rrset = RRset(name, rrclass, rrtype, RRTTL(60))
+ rrset.add_rdata(Rdata(rrtype, rrclass, '192.0.2.1'))
+ self.assertEqual(None, rrsets.add_rrset(rrset))
+ self.check_find_result(rrsets)
+
+ # duplicate add is (at least currently) rejected
+ self.assertRaises(ValueError, rrsets.add_rrset, rrset)
+
+ # remove it, then we cannot find it any more.
+ self.assertTrue(rrsets.remove_rrset(name, rrclass, rrtype))
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # duplicate remove (specified RRset doesn't exist) reulsts in False
+ self.assertFalse(rrsets.remove_rrset(name, rrclass, rrtype))
+
+ # Checking bad args
+ self.assertRaises(TypeError, rrsets.add_rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset, rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset)
+
+ self.assertRaises(TypeError, rrsets.remove_rrset, 1, rrclass, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, 1, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass, 1)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrtype,
+ rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass,
+ rrtype, 1)
+
+ def test_empty_class(self):
+ # A user defined collection class shouldn't cause disruption.
+ class EmptyRRsetCollection(RRsetCollectionBase):
+ def __init__(self):
+ pass
+ rrsets = EmptyRRsetCollection()
+ self.assertRaises(TypeError, rrsets.find, Name('www.example.org'),
+ RRClass.IN(), RRType.A())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tests/zone_checker_python_test.py b/src/lib/dns/python/tests/zone_checker_python_test.py
new file mode 100644
index 0000000..66b6c47
--- /dev/null
+++ b/src/lib/dns/python/tests/zone_checker_python_test.py
@@ -0,0 +1,179 @@
+# 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.
+
+import unittest
+import sys
+from pydnspp import *
+
+# A separate exception class raised from some tests to see if it's propagated.
+class FakeException(Exception):
+ pass
+
+class ZoneCheckerTest(unittest.TestCase):
+ def __callback(self, reason, reasons):
+ # Issue callback for check_zone(). It simply records the given reason
+ # string in the given list.
+ reasons.append(reason)
+
+ def test_check(self):
+ errors = []
+ warns = []
+
+ # A successful case with no warning.
+ rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
+ b'example.org. 0 NS ns.example.org.\n' +
+ b'ns.example.org. 0 A 192.0.2.1\n',
+ Name('example.org'), RRClass.IN())
+ self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
+ rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ # Check fails and one additional warning.
+ rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
+ Name('example.org'), RRClass.IN())
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
+ self.assertEqual(['zone example.org/IN: NS has no address records ' +
+ '(A or AAAA)'], warns)
+
+ # Same RRset collection, suppressing callbacks
+ errors = []
+ warns = []
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (None, None)))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ def test_check_badarg(self):
+ rrsets = RRsetCollection()
+ # Bad types
+ self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ 1, (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, 1)
+
+ # Bad callbacks
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (1, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, 1))
+
+ # Extra/missing args
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None), 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets)
+ check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
+
+ def test_check_callback_fail(self):
+ # Let the call raise a Python exception. It should be propagated to
+ # the top level.
+ def __bad_callback(reason):
+ raise FakeException('error in callback')
+
+ # Using an empty collection, triggering an error callback.
+ self.assertRaises(FakeException, check_zone, Name('example.org'),
+ RRClass.IN(), RRsetCollection(),
+ (__bad_callback, None))
+
+ # An unusual case: the callback is expected to return None, but if it
+ # returns an actual object it shouldn't cause leak inside the callback.
+ class RefChecker:
+ pass
+ def __callback(reason, checker):
+ return checker
+
+ ref_checker = RefChecker()
+ orig_refcnt = sys.getrefcount(ref_checker)
+ check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
+ (lambda r: __callback(r, ref_checker), None))
+ self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
+
+ def test_check_custom_collection(self):
+ # Test if check_zone() works with pure-Python RRsetCollection.
+
+ class FakeRRsetCollection(RRsetCollectionBase):
+ # This is the Python-only collection class. Its find() makes
+ # the check pass by default, by returning hardcoded RRsets.
+ # If raise_on_find is set to True, find() raises an exception.
+ # If find_result is set to something other than 'use_default'
+ # (as a string), find() returns that specified value (note that
+ # it can be None).
+
+ def __init__(self, raise_on_find=False, find_result='use_default'):
+ self.__raise_on_find = raise_on_find
+ self.__find_result = find_result
+
+ def find(self, name, rrclass, rrtype):
+ if self.__raise_on_find:
+ raise FakeException('find error')
+ if self.__find_result is not 'use_default':
+ return self.__find_result
+ if rrtype == RRType.SOA():
+ soa = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+ '. . 0 0 0 0 0'))
+ return soa
+ if rrtype == RRType.NS():
+ ns = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
+ 'example.org'))
+ return ns
+ return None
+
+ # A successful case. Just checking it works in that case.
+ rrsets = FakeRRsetCollection()
+ self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Likewise, normal case but zone check fails.
+ rrsets = FakeRRsetCollection(False, None)
+ self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Our find() returns a bad type of result.
+ rrsets = FakeRRsetCollection(False, 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None))
+
+ # Our find() returns an empty SOA RRset. C++ zone checker code
+ # throws, which results in IscException.
+ rrsets = FakeRRsetCollection(False, RRset(Name('example'),
+ RRClass.IN(),
+ RRType.SOA(), RRTTL(0)))
+ self.assertRaises(IscException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+ # Our find() raises an exception. That exception is propagated to
+ # the top level.
+ rrsets = FakeRRsetCollection(True)
+ self.assertRaises(FakeException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
new file mode 100644
index 0000000..eaad72d
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.cc
@@ -0,0 +1,229 @@
+// 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/zone_checker_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+#include <dns/zone_checker.h>
+
+#include <boost/bind.hpp>
+
+#include <cstring>
+#include <string>
+#include <stdexcept>
+
+using std::string;
+using isc::util::python::PyObjectContainer;
+using namespace isc::dns;
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+// Place the exception class in a named namespace to avoid weird run time
+// failure with clang++. See isc.log Python wrapper.
+namespace clang_unnamed_namespace_workaround {
+// This is used to abort check_zone() and go back to the top level.
+// We use a separate exception so it won't be caught in the middle.
+class InternalException : public std::exception {
+};
+}
+using namespace clang_unnamed_namespace_workaround;
+
+namespace {
+// This is a "wrapper" RRsetCollection subclass. It's constructed with
+// a Python RRsetCollection object, and its find() calls the Python version
+// of RRsetCollection.find(). This way, the check_zone() wrapper will work
+// for pure-Python RRsetCollection classes, too.
+class PyRRsetCollection : public RRsetCollectionBase {
+public:
+ PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
+
+ virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const {
+ try {
+ // Convert C++ args to Python objects, and builds argument tuple
+ // to the Python method. This should basically succeed.
+ PyObjectContainer poc_name(createNameObject(name));
+ PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
+ PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
+ PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
+ po_rrsets_,
+ poc_name.get(),
+ poc_rrclass.get(),
+ poc_rrtype.get()));
+
+ // Call the Python method.
+ // PyObject_CallMethod is dirty and requires mutable C-string for
+ // method name and arguments. While it's unlikely for these to
+ // be modified, we err on the side of caution and make copies.
+ char method_name[sizeof("find")];
+ char method_args[sizeof("(OOO)")];
+ std::strcpy(method_name, "find");
+ std::strcpy(method_args, "(OOO)");
+ PyObjectContainer poc_result(
+ PyObject_CallMethod(po_rrsets_, method_name, method_args,
+ poc_name.get(), poc_rrclass.get(),
+ poc_rrtype.get()));
+ PyObject* const po_result = poc_result.get();
+ if (po_result == Py_None) {
+ return (ConstRRsetPtr());
+ } else if (PyRRset_Check(po_result)) {
+ return (PyRRset_ToRRsetPtr(po_result));
+ } else {
+ PyErr_SetString(PyExc_TypeError, "invalid type for "
+ "RRsetCollection.find(): must be None "
+ "or RRset");
+ throw InternalException();
+ }
+ } catch (const isc::util::python::PyCPPWrapperException& ex) {
+ // This normally means the method call fails. Propagate the
+ // already-set Python error to the top level. Other C++ exceptions
+ // are really unexpected, so we also (implicitly) propagate it
+ // to the top level and recognize it as "unexpected failure".
+ throw InternalException();
+ }
+ }
+
+ virtual IterPtr getBeginning() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+ virtual IterPtr getEnd() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+
+private:
+ PyObject* const po_rrsets_;
+};
+
+void
+callback(const string& reason, PyObject* obj) {
+ PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
+ reason.size()));
+ PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
+ if (po_result == NULL) {
+ throw InternalException();
+ }
+ Py_DECREF(po_result);
+}
+
+ZoneCheckerCallbacks::IssueCallback
+PyCallable_ToCallback(PyObject* obj) {
+ if (obj == Py_None) {
+ return (NULL);
+ }
+ return (boost::bind(callback, _1, obj));
+}
+
+}
+
+PyObject*
+pyCheckZone(PyObject*, PyObject* args) {
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrsets;
+ PyObject* po_error;
+ PyObject* po_warn;
+
+ if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
+ &po_rrsets, &po_error, &po_warn)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "zone_name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
+ }
+ if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
+ return (setTypeError(po_rrsets, "zone_rrsets",
+ "RRsetCollectionBase"));
+ }
+ if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
+ return (setTypeError(po_error, "error", "callable or None"));
+ }
+ if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
+ return (setTypeError(po_warn, "warn", "callable or None"));
+ }
+
+ PyRRsetCollection py_rrsets(po_rrsets);
+ if (checkZone(PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass), py_rrsets,
+ ZoneCheckerCallbacks(
+ PyCallable_ToCallback(po_error),
+ PyCallable_ToCallback(po_warn)))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (const InternalException& ex) {
+ // Normally, error string should have been set already. For some
+ // rare cases such as memory allocation failure, we set the last-resort
+ // error string.
+ if (PyErr_Occurred() == NULL) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in check_zone()");
+ }
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in check_zone(): " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+} // namespace internal
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/zone_checker_python.h b/src/lib/dns/python/zone_checker_python.h
new file mode 100644
index 0000000..63168fd
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.h
@@ -0,0 +1,35 @@
+// 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 PYTHON_ZONE_CHECKER_H
+#define PYTHON_ZONE_CHECKER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+PyObject* pyCheckZone(PyObject* self, PyObject* args);
+
+} // namespace python
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/zone_checker_python_inc.cc b/src/lib/dns/python/zone_checker_python_inc.cc
new file mode 100644
index 0000000..c99042e
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python_inc.cc
@@ -0,0 +1,79 @@
+namespace {
+// Modifications
+// - callbacks => (error, warn)
+// - recover paragraph before itemization (it's a bug of convert script)
+// - correct broken format for nested items (another bug of script)
+// - true/false => True/False
+// - removed Exception section (for simplicity)
+const char* const dns_checkZone_doc = "\
+check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
+\n\
+Perform basic integrity checks on zone RRsets.\n\
+\n\
+This function performs some lightweight checks on zone's SOA and\n\
+(apex) NS records. Here, lightweight means it doesn't require\n\
+traversing the entire zone, and should be expected to complete\n\
+reasonably quickly regardless of the size of the zone.\n\
+\n\
+It distinguishes \"critical\" errors and other undesirable issues: the\n\
+former should be interpreted as the resulting zone shouldn't be used\n\
+further, e.g, by an authoritative server implementation; the latter\n\
+means the issues are better to be addressed but are not necessarily\n\
+considered to make the zone invalid. Critical errors are reported via\n\
+the error() function, and non critical issues are reported via warn().\n\
+\n\
+Specific checks performed by this function is as follows. Failure of\n\
+a check is considered a critical error unless noted otherwise:\n\
+\n\
+- There is exactly one SOA RR at the zone apex.\n\
+- There is at least one NS RR at the zone apex.\n\
+- For each apex NS record, if the NS name (the RDATA of the record) is\n\
+ in the zone (i.e., it's a subdomain of the zone origin and above any\n\
+ zone cut due to delegation), check the following:\n\
+ - the NS name should have an address record (AAAA or A). Failure of\n\
+ this check is considered a non critical issue.\n\
+ - the NS name does not have a CNAME. This is prohibited by Section\n\
+ 10.3 of RFC 2181.\n\
+ - the NS name is not subject to DNAME substitution. This is prohibited\n\
+ by Section 4 of RFC 6672.\n\
+\n\
+In addition, when the check is completed without any\n\
+critical error, this function guarantees that RRsets for the SOA and\n\
+(apex) NS stored in the passed RRset collection have the expected\n\
+type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
+respectively. (This is normally expected to be the case, but not\n\
+guaranteed by the API).\n\
+\n\
+As for the check on the existence of AAAA or A records for NS names,\n\
+it should be noted that BIND 9 treats this as a critical error. It's\n\
+not clear whether it's an implementation dependent behavior or based\n\
+on the protocol standard (it looks like the former), but to make it\n\
+sure we need to confirm there is even no wildcard match for the names.\n\
+This should be a very rare configuration, and more expensive to\n\
+detect, so we do not check this condition, and treat this case as a\n\
+non critical issue.\n\
+\n\
+This function indicates the result of the checks (whether there is a\n\
+critical error) via the return value: It returns True if there is no\n\
+critical error and returns False otherwise. It doesn't throw an\n\
+exception on encountering an error so that it can report as many\n\
+errors as possible in a single call. If an exception is a better way\n\
+to signal the error, the caller can pass a callable object as error()\n\
+that throws.\n\
+\n\
+This function can still throw an exception if it finds a really bogus\n\
+condition that is most likely to be an implementation bug of the\n\
+caller. Such cases include when an RRset contained in the RRset\n\
+collection is empty.\n\
+\n\
+Parameters:\n\
+ zone_name The name of the zone to be checked\n\
+ zone_class The RR class of the zone to be checked\n\
+ zone_rrsets The collection of RRsets of the zone\n\
+ error Callable object used to report errors\n\
+ warn Callable object used to report non-critical issues\n\
+\n\
+Return Value(s): True if no critical errors are found; False\n\
+otherwise.\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 4cd63cc..3fe0c74 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -60,7 +60,7 @@ public:
///
/// \brief A standard DNS module exception that is thrown if RDATA parser
-/// parser encounters a character-string (as defined in RFC1035) exceeding
+/// encounters a character-string (as defined in RFC1035) exceeding
/// the maximum allowable length (\c MAX_CHARSTRING_LEN).
///
class CharStringTooLong : public Exception {
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index fb4c9b4..4c8965a 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -14,9 +14,11 @@
#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/master_lexer.h>
#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
#include <boost/lexical_cast.hpp>
@@ -57,8 +59,8 @@ decimalToNumber(const char* s, const char* s_end) {
}
void
-strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result)
+stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result)
{
// make a space for the 1-byte length field; filled in at the end
result.push_back(0);
@@ -91,6 +93,83 @@ strToCharString(const MasterToken::StringRegion& str_region,
result[0] = result.size() - 1;
}
+std::string
+charStringToString(const CharString& char_string) {
+ std::string s;
+ for (CharString::const_iterator it = char_string.begin() + 1;
+ it != char_string.end(); ++it) {
+ const uint8_t ch = *it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
+int compareCharStrings(const detail::CharString& self,
+ const detail::CharString& other) {
+ if (self.size() == 0 && other.size() == 0) {
+ return (0);
+ }
+ if (self.size() == 0) {
+ return (-1);
+ }
+ if (other.size() == 0) {
+ return (1);
+ }
+ const size_t self_len = self[0];
+ const size_t other_len = other[0];
+ const size_t cmp_len = std::min(self_len, other_len);
+ const int cmp = std::memcmp(&self[1], &other[1], cmp_len);
+ if (cmp < 0) {
+ return (-1);
+ } else if (cmp > 0) {
+ return (1);
+ } else if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+size_t
+bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target) {
+ if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "insufficient data to read character-string length");
+ }
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ if (buffer.getLength() - buffer.getPosition() < len) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "not enough data in buffer to read character-string of len"
+ << static_cast<int>(len));
+ }
+
+ target.resize(len + 1);
+ target[0] = len;
+ buffer.readData(&target[0] + 1, len);
+
+ return (len + 1);
+}
+
} // end of detail
} // end of generic
} // end of rdata
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 702af04..8e3e294 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -17,7 +17,9 @@
#include <dns/master_lexer.h>
+#include <string>
#include <vector>
+#include <algorithm>
#include <stdint.h>
namespace isc {
@@ -48,8 +50,54 @@ typedef std::vector<uint8_t> CharString;
/// \brief str_region A string that represents a character-string.
/// \brief result A placeholder vector where the resulting data are to be
/// stored. Expected to be empty, but it's not checked.
-void strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result);
+void stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
+
+/// \brief Compare two CharString objects
+///
+/// \param self The CharString field to compare
+/// \param other The CharString field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+/// 1 if \c self would be sorted after \c other
+/// 0 if \c self and \c other are equal
+int compareCharStrings(const CharString& self, const CharString& other);
+
+/// \brief Convert a buffer containing a character-string to CharString
+///
+/// This method reads one character-string from the given buffer (in wire
+/// format) and places the result in the given \c CharString object.
+/// Since this is expected to be used in message parsing, the exception it
+/// raises is of that type.
+///
+/// On success, the buffer position is advanced to the end of the char-string,
+/// and the number of bytes read is returned.
+///
+/// \param buffer The buffer to read from.
+/// \param rdata_len The total size of the rr's rdata currently being read
+/// (used for integrity checks in the wire data)
+/// \param target The \c CharString where the result will be stored. Any
+/// existing data in the target will be overwritten.
+/// \throw DNSMessageFORMERR If the available data is not enough to read
+/// the character-string, or if the character-string length is out of bounds
+/// \return The number of bytes read
+size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target);
+
} // namespace detail
} // namespace generic
diff --git a/src/lib/dns/rdata/generic/detail/lexer_util.h b/src/lib/dns/rdata/generic/detail/lexer_util.h
new file mode 100644
index 0000000..89df5a0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/lexer_util.h
@@ -0,0 +1,70 @@
+// 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 DNS_RDATA_LEXER_UTIL_H
+#define DNS_RDATA_LEXER_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+
+/// \file lexer_util.h
+/// \brief Utilities for extracting RDATA fields from lexer.
+///
+/// This file intends to define convenient small routines that can be
+/// commonly used in the RDATA implementation to build RDATA fields from
+/// a \c MasterLexer.
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Construct a Name object using a master lexer and optional origin.
+///
+/// This is a convenient shortcut of commonly used code pattern that would
+/// be used to build RDATA that contain a domain name field.
+///
+/// Note that this function throws an exception against invalid input.
+/// The (direct or indirect) caller's responsibility needs to expect and
+/// handle exceptions appropriately.
+///
+/// \throw MasterLexer::LexerError The next token from lexer is not string.
+/// \throw Other Exceptions from the \c Name class constructor if the next
+/// string token from the lexer does not represent a valid name.
+///
+/// \param lexer A \c MasterLexer object. Its next token is expected to be
+/// a string that represent a domain name.
+/// \param origin If non NULL, specifies the origin of the name to be
+/// constructed.
+///
+/// \return A new Name object that corresponds to the next string token of
+/// the \c lexer.
+inline Name
+createNameFromLexer(MasterLexer& lexer, const Name* origin) {
+ const MasterToken::StringRegion& str_region =
+ lexer.getNextToken(MasterToken::STRING).getStringRegion();
+ return (Name(str_region.beg, str_region.len, origin));
+}
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_LEXER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index d1916e3..b48d109 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -119,7 +119,7 @@ private:
break;
}
string_list_.push_back(std::vector<uint8_t>());
- strToCharString(token.getStringRegion(), string_list_.back());
+ stringToCharString(token.getStringRegion(), string_list_.back());
}
// Let upper layer handle eol/eof.
@@ -177,18 +177,14 @@ public:
toText() const {
std::string s;
- // XXX: this implementation is not entirely correct. for example, it
- // should escape double-quotes if they appear in the character string.
for (std::vector<std::vector<uint8_t> >::const_iterator it =
- string_list_.begin();
- it != string_list_.end();
- ++it)
+ string_list_.begin(); it != string_list_.end(); ++it)
{
if (!s.empty()) {
s.push_back(' ');
}
s.push_back('"');
- s.insert(s.end(), (*it).begin() + 1, (*it).end());
+ s.append(charStringToString(*it));
s.push_back('"');
}
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
index 77bfdfd..319ec7d 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.cc
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -14,66 +14,105 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
-#include <dns/character_string.h>
-#include <util/strutil.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
using namespace std;
-using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl {
+public:
+ HINFOImpl(const std::string& hinfo_str) {
+ std::istringstream ss(hinfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseHINFOData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid HINFO text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+ << hinfo_str << "': " << ex.what());
+ }
+ }
-HINFO::HINFO(const std::string& hinfo_str) {
- string::const_iterator input_iterator = hinfo_str.begin();
+ HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "HINFO RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
- bool quoted;
- cpu_ = getNextCharacterString(hinfo_str, input_iterator, "ed);
+ HINFOImpl(MasterLexer& lexer)
+ {
+ parseHINFOData(lexer);
+ }
- skipLeftSpaces(hinfo_str, input_iterator, quoted);
+private:
+ void
+ parseHINFOData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), cpu);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), os);
+ }
- os_ = getNextCharacterString(hinfo_str, input_iterator);
+public:
+ detail::CharString cpu;
+ detail::CharString os;
+};
- // Skip whitespace at the end.
- while (input_iterator < hinfo_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
- if (input_iterator < hinfo_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: too many fields.");
- }
-}
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
-HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) {
- cpu_ = getNextCharacterString(buffer, rdata_len);
- os_ = getNextCharacterString(buffer, rdata_len);
-}
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new HINFOImpl(buffer, rdata_len))
+{}
HINFO::HINFO(const HINFO& source):
- Rdata(), cpu_(source.cpu_), os_(source.os_)
+ Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
{
+ impl_.reset(new HINFOImpl(*source.impl_));
+ return (*this);
+}
+
+HINFO::~HINFO() {
}
std::string
HINFO::toText() const {
string result;
result += "\"";
- result += cpu_;
+ result += detail::charStringToString(impl_->cpu);
result += "\" \"";
- result += os_;
+ result += detail::charStringToString(impl_->os);
result += "\"";
return (result);
}
@@ -92,49 +131,28 @@ int
HINFO::compare(const Rdata& other) const {
const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
- if (cpu_ < other_hinfo.cpu_) {
- return (-1);
- } else if (cpu_ > other_hinfo.cpu_) {
- return (1);
- }
-
- if (os_ < other_hinfo.os_) {
- return (-1);
- } else if (os_ > other_hinfo.os_) {
- return (1);
+ const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+ if (cmp != 0) {
+ return (cmp);
}
-
- return (0);
+ return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
}
-const std::string&
+const std::string
HINFO::getCPU() const {
- return (cpu_);
+ return (detail::charStringToString(impl_->cpu));
}
-const std::string&
+const std::string
HINFO::getOS() const {
- return (os_);
+ return (detail::charStringToString(impl_->os));
}
+template <typename T>
void
-HINFO::skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool optional)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: field is missing.");
- }
-
- if (!isspace(*input_iterator) && !optional) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: fields are not separated by space.");
- }
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
+HINFO::toWireHelper(T& outputer) const {
+ outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+ outputer.writeData(&impl_->os[0], impl_->os.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/hinfo_13.h b/src/lib/dns/rdata/generic/hinfo_13.h
index 5534a7e..60404c9 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.h
+++ b/src/lib/dns/rdata/generic/hinfo_13.h
@@ -17,6 +17,9 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -28,6 +31,8 @@
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl;
+
/// \brief \c HINFO class represents the HINFO rdata defined in
/// RFC1034, RFC1035
///
@@ -40,38 +45,21 @@ public:
// END_COMMON_MEMBERS
// HINFO specific methods
- const std::string& getCPU() const;
- const std::string& getOS() const;
+ ~HINFO();
-private:
- /// Skip the left whitespaces of the input string
- ///
- /// If \c optional argument is \c true and no spaces occur at the
- /// current location, then nothing happens. If \c optional is
- /// \c false and no spaces occur at the current location, then
- /// the \c InvalidRdataText exception is thrown.
- ///
- /// \param input_str The input string
- /// \param input_iterator From which the skipping started
- /// \param optional If true, the spaces are optionally skipped.
- void skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool optional);
+ HINFO& operator=(const HINFO&);
+
+ const std::string getCPU() const;
+ const std::string getOS() const;
+private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint8(cpu_.size());
- outputer.writeData(cpu_.c_str(), cpu_.size());
-
- outputer.writeUint8(os_.size());
- outputer.writeData(os_.c_str(), os_.size());
- }
+ void toWireHelper(T& outputer) const;
- std::string cpu_;
- std::string os_;
+ boost::scoped_ptr<HINFOImpl> impl_;
};
diff --git a/src/lib/dns/rdata/generic/naptr_35.cc b/src/lib/dns/rdata/generic/naptr_35.cc
index 0958eff..78bf472 100644
--- a/src/lib/dns/rdata/generic/naptr_35.cc
+++ b/src/lib/dns/rdata/generic/naptr_35.cc
@@ -14,119 +14,147 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/character_string.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-namespace {
-/// Skip the left whitespaces of the input string
-///
-/// \param input_str The input string
-/// \param input_iterator From which the skipping started
-void
-skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, field is missing.");
+class NAPTRImpl {
+public:
+ NAPTRImpl() : replacement(".") {}
+
+ NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+ if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: insufficient length ");
+ }
+ order = buffer.readUint16();
+ preference = buffer.readUint16();
+ rdata_len -= 4;
+
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+ replacement = Name(buffer);
+ if (rdata_len < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: missing replacement name");
+ }
+ rdata_len -= replacement.getLength();
+
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "NAPTR RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
}
- if (!isspace(*input_iterator)) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, fields are not separated by space.");
+ NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+ std::istringstream ss(naptr_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseNAPTRData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+ << naptr_str << "': " << ex.what());
+ }
}
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
-}
-} // Anonymous namespace
+ NAPTRImpl(MasterLexer& lexer) : replacement(".")
+ {
+ parseNAPTRData(lexer);
+ }
-NAPTR::NAPTR(InputBuffer& buffer, size_t len):
- replacement_(".")
-{
- order_ = buffer.readUint16();
- preference_ = buffer.readUint16();
+private:
+ void
+ parseNAPTRData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: order out of range: "
+ << token.getNumber());
+ }
+ order = token.getNumber();
+ token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: preference out of range: "
+ << token.getNumber());
+ }
+ preference = token.getNumber();
+
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), flags);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), services);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), regexp);
+
+ token = lexer.getNextToken(MasterToken::STRING);
+ replacement = Name(token.getString());
+ }
- flags_ = getNextCharacterString(buffer, len);
- services_ = getNextCharacterString(buffer, len);
- regexp_ = getNextCharacterString(buffer, len);
- replacement_ = Name(buffer);
-}
-NAPTR::NAPTR(const std::string& naptr_str):
- replacement_(".")
-{
- istringstream iss(naptr_str);
+public:
uint16_t order;
uint16_t preference;
-
- iss >> order >> preference;
-
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NAPTR text format");
- }
-
- order_ = order;
- preference_ = preference;
-
- string::const_iterator input_iterator = naptr_str.begin() + iss.tellg();
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- flags_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- services_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- regexp_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- if (input_iterator < naptr_str.end()) {
- string replacementStr(input_iterator, naptr_str.end());
-
- replacement_ = Name(replacementStr);
- } else {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, replacement field is missing");
- }
+ detail::CharString flags;
+ detail::CharString services;
+ detail::CharString regexp;
+ Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) : Rdata(),
+ impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+ impl_.reset(new NAPTRImpl(*source.impl_));
+ return (*this);
}
-NAPTR::NAPTR(const NAPTR& naptr):
- Rdata(), order_(naptr.order_), preference_(naptr.preference_),
- flags_(naptr.flags_), services_(naptr.services_), regexp_(naptr.regexp_),
- replacement_(naptr.replacement_)
-{
+NAPTR::~NAPTR() {
}
void
NAPTR::toWire(OutputBuffer& buffer) const {
toWireHelper(buffer);
- replacement_.toWire(buffer);
-
+ impl_->replacement.toWire(buffer);
}
void
@@ -134,23 +162,23 @@ NAPTR::toWire(AbstractMessageRenderer& renderer) const {
toWireHelper(renderer);
// Type NAPTR is not "well-known", and name compression must be disabled
// per RFC3597.
- renderer.writeName(replacement_, false);
+ renderer.writeName(impl_->replacement, false);
}
string
NAPTR::toText() const {
string result;
- result += lexical_cast<string>(order_);
+ result += lexical_cast<string>(impl_->order);
result += " ";
- result += lexical_cast<string>(preference_);
+ result += lexical_cast<string>(impl_->preference);
result += " \"";
- result += flags_;
+ result += detail::charStringToString(impl_->flags);
result += "\" \"";
- result += services_;
+ result += detail::charStringToString(impl_->services);
result += "\" \"";
- result += regexp_;
+ result += detail::charStringToString(impl_->regexp);
result += "\" ";
- result += replacement_.toText();
+ result += impl_->replacement.toText();
return (result);
}
@@ -158,67 +186,78 @@ int
NAPTR::compare(const Rdata& other) const {
const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
- if (order_ < other_naptr.order_) {
+ if (impl_->order < other_naptr.impl_->order) {
return (-1);
- } else if (order_ > other_naptr.order_) {
+ } else if (impl_->order > other_naptr.impl_->order) {
return (1);
}
- if (preference_ < other_naptr.preference_) {
+ if (impl_->preference < other_naptr.impl_->preference) {
return (-1);
- } else if (preference_ > other_naptr.preference_) {
+ } else if (impl_->preference > other_naptr.impl_->preference) {
return (1);
}
- if (flags_ < other_naptr.flags_) {
- return (-1);
- } else if (flags_ > other_naptr.flags_) {
- return (1);
+ const int fcmp = detail::compareCharStrings(impl_->flags,
+ other_naptr.impl_->flags);
+ if (fcmp != 0) {
+ return (fcmp);
}
- if (services_ < other_naptr.services_) {
- return (-1);
- } else if (services_ > other_naptr.services_) {
- return (1);
+ const int scmp = detail::compareCharStrings(impl_->services,
+ other_naptr.impl_->services);
+ if (scmp != 0) {
+ return (scmp);
}
- if (regexp_ < other_naptr.regexp_) {
- return (-1);
- } else if (regexp_ > other_naptr.regexp_) {
- return (1);
+ const int rcmp = detail::compareCharStrings(impl_->regexp,
+ other_naptr.impl_->regexp);
+ if (rcmp != 0) {
+ return (rcmp);
}
- return (compareNames(replacement_, other_naptr.replacement_));
+ return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
}
uint16_t
NAPTR::getOrder() const {
- return (order_);
+ return (impl_->order);
}
uint16_t
NAPTR::getPreference() const {
- return (preference_);
+ return (impl_->preference);
}
-const std::string&
+const std::string
NAPTR::getFlags() const {
- return (flags_);
+ return (detail::charStringToString(impl_->flags));
}
-const std::string&
+const std::string
NAPTR::getServices() const {
- return (services_);
+ return (detail::charStringToString(impl_->services));
}
-const std::string&
+const std::string
NAPTR::getRegexp() const {
- return (regexp_);
+ return (detail::charStringToString(impl_->regexp));
}
const Name&
NAPTR::getReplacement() const {
- return (replacement_);
+ return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+ outputer.writeUint16(impl_->order);
+ outputer.writeUint16(impl_->preference);
+
+ outputer.writeData(&impl_->flags[0], impl_->flags.size());
+ outputer.writeData(&impl_->services[0], impl_->services.size());
+ outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/naptr_35.h b/src/lib/dns/rdata/generic/naptr_35.h
index c82ef87..a2e2cae 100644
--- a/src/lib/dns/rdata/generic/naptr_35.h
+++ b/src/lib/dns/rdata/generic/naptr_35.h
@@ -16,6 +16,8 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -27,6 +29,8 @@
// BEGIN_RDATA_NAMESPACE
+class NAPTRImpl;
+
/// \brief \c NAPTR class represents the NAPTR rdata defined in
/// RFC2915, RFC2168 and RFC3403
///
@@ -39,37 +43,24 @@ public:
// END_COMMON_MEMBERS
// NAPTR specific methods
+ ~NAPTR();
+
+ NAPTR& operator=(const NAPTR& source);
+
uint16_t getOrder() const;
uint16_t getPreference() const;
- const std::string& getFlags() const;
- const std::string& getServices() const;
- const std::string& getRegexp() const;
+ const std::string getFlags() const;
+ const std::string getServices() const;
+ const std::string getRegexp() const;
const Name& getReplacement() const;
private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint16(order_);
- outputer.writeUint16(preference_);
-
- outputer.writeUint8(flags_.size());
- outputer.writeData(flags_.c_str(), flags_.size());
-
- outputer.writeUint8(services_.size());
- outputer.writeData(services_.c_str(), services_.size());
-
- outputer.writeUint8(regexp_.size());
- outputer.writeData(regexp_.c_str(), regexp_.size());
- }
+ void toWireHelper(T& outputer) const;
- uint16_t order_;
- uint16_t preference_;
- std::string flags_;
- std::string services_;
- std::string regexp_;
- Name replacement_;
+ boost::scoped_ptr<NAPTRImpl> impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index 3ff2f08..56eb7a2 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -14,22 +14,29 @@
#include <config.h>
-#include <string>
-
-#include <boost/static_assert.hpp>
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -42,35 +49,85 @@ SOA::SOA(InputBuffer& buffer, size_t) :
buffer.readData(numdata_, sizeof(numdata_));
}
+namespace {
+void
+fillParameters(MasterLexer& lexer, uint8_t numdata[20]) {
+ // Copy serial, refresh, retry, expire, minimum. We accept the extended
+ // TTL-compatible style for the latter four.
+ OutputBuffer buffer(20);
+ buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ for (int i = 0; i < 4; ++i) {
+ buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING).
+ getString()).getValue());
+ }
+ memcpy(numdata, buffer.getData(), buffer.getLength());
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SOA 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 MNAME and RNAME must be absolute since there's no parameter that
+/// specifies the origin name; if these are not absolute, \c MissingNameOrigin
+/// exception will be thrown. These must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
SOA::SOA(const std::string& soastr) :
- mname_("."), rname_(".") // quick hack workaround
+ // Fill in dummy name and replace them soon below.
+ mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME())
{
- istringstream iss(soastr);
- string token;
-
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA MNAME");
+ try {
+ std::istringstream ss(soastr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mname_ = createNameFromLexer(lexer, NULL);
+ rname_ = createNameFromLexer(lexer, NULL);
+ fillParameters(lexer, numdata_);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SOA: "
+ << soastr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SOA from '" <<
+ soastr << "': " << ex.what());
}
- mname_ = Name(token);
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA RNAME");
- }
- rname_ = Name(token);
+}
- uint32_t serial, refresh, retry, expire, minimum;
- iss >> serial >> refresh >> retry >> expire >> minimum;
- if (iss.rdstate() != ios::eofbit) {
- isc_throw(InvalidRdataText, "Invalid SOA format");
- }
- OutputBuffer buffer(20);
- buffer.writeUint32(serial);
- buffer.writeUint32(refresh);
- buffer.writeUint32(retry);
- buffer.writeUint32(expire);
- buffer.writeUint32(minimum);
- memcpy(numdata_, buffer.getData(), buffer.getLength());
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SOA RDATA. The MNAME and RNAME fields can be non absolute if
+/// \c origin is non NULL, in which case \c origin is used to make them
+/// absolute. These must not be represented as a quoted string.
+///
+/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid
+/// decimal representation of an unsigned 32-bit integer or other
+/// valid textual representation of \c RRTTL such as "1H" (which means 3600).
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL 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 MNAME and RNAME when
+/// they are non absolute.
+SOA::SOA(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mname_(createNameFromLexer(lexer, origin)),
+ rname_(createNameFromLexer(lexer, origin))
+{
+ fillParameters(lexer, numdata_);
}
SOA::SOA(const Name& mname, const Name& rname, uint32_t serial,
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index 70d6b78..1ff4163 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -22,6 +22,8 @@
#include <exceptions/exceptions.h>
+#include <boost/optional.hpp>
+
namespace isc {
namespace util {
class InputBuffer;
@@ -33,6 +35,16 @@ namespace dns {
// forward declarations
class AbstractMessageRenderer;
+class RRClass; // forward declaration to define MaybeRRClass.
+
+/// \brief A shortcut for a compound type to represent RRClass-or-not.
+///
+/// A value of this type can be interpreted in a boolean context, whose
+/// value is \c true if and only if it contains a valid RRClass object.
+/// And, if it contains a valid RRClass object, its value is accessible
+/// using \c operator*, just like a bare pointer to \c RRClass.
+typedef boost::optional<RRClass> MaybeRRClass;
+
///
/// \brief A standard DNS module exception that is thrown if an RRClass object
/// is being constructed from an unrecognized string.
@@ -123,8 +135,8 @@ public:
/// If the given string is not recognized as a valid representation of
/// an RR class, an exception of class \c InvalidRRClass will be thrown.
///
- /// \param classstr A string representation of the \c RRClass
- explicit RRClass(const std::string& classstr);
+ /// \param class_str A string representation of the \c RRClass
+ explicit RRClass(const std::string& class_str);
/// Constructor from wire-format data.
///
/// The \c buffer parameter normally stores a complete DNS message
@@ -136,6 +148,45 @@ public:
///
/// \param buffer A buffer storing the wire format data.
explicit RRClass(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRClass from text.
+ ///
+ /// This static method is similar to the constructor that takes a
+ /// string object, but works as a factory and reports parsing
+ /// failure in the form of the return value. Normally the
+ /// constructor version should suffice, but in some cases the caller
+ /// may have to expect mixture of valid and invalid input, and may
+ /// want to minimize the overhead of possible exception handling.
+ /// This version is provided for such purpose.
+ ///
+ /// For the format of the \c class_str argument, see the
+ /// <code>RRClass(const std::string&)</code> constructor.
+ ///
+ /// If the given text represents a valid RRClass, it returns a
+ /// \c MaybeRRClass object that stores a corresponding \c RRClass
+ /// object, which is accessible via \c operator*(). In this case
+ /// the returned object will be interpreted as \c true in a boolean
+ /// context. If the given text does not represent a valid RRClass,
+ /// it returns a \c MaybeRRClass object which is interpreted as
+ /// \c false in a boolean context.
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR class. For
+ /// this reason this function intentionally omits the capability of
+ /// delivering a detailed reason for the parse failure, such as in the
+ /// \c want() string when exception is thrown from the constructor
+ /// (it will internally require a creation of string object, which
+ /// is relatively expensive). If such detailed information is
+ /// necessary, the constructor version should be used to catch the
+ /// resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRClass exception.
+ ///
+ /// \param class_str A string representation of the \c RRClass.
+ /// \return A MaybeRRClass object either storing an RRClass object
+ /// for the given text or a \c false value.
+ static MaybeRRClass createFromText(const std::string& class_str);
+
///
/// We use the default copy constructor intentionally.
//@}
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index ac5823c..76a2e73 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -30,8 +30,11 @@ using namespace isc::util;
namespace isc {
namespace dns {
-RRClass::RRClass(const std::string& classstr) {
- classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
+RRClass::RRClass(const std::string& class_str) {
+ if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode_)) {
+ isc_throw(InvalidRRClass,
+ "Unrecognized RR class string: " + class_str);
+ }
}
RRClass::RRClass(InputBuffer& buffer) {
@@ -56,6 +59,16 @@ RRClass::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeUint16(classcode_);
}
+MaybeRRClass
+RRClass::createFromText(const string& class_str) {
+ uint16_t class_code;
+ if (RRParamRegistry::getRegistry().textToClassCode(class_str,
+ class_code)) {
+ return (MaybeRRClass(class_code));
+ }
+ return (MaybeRRClass());
+}
+
ostream&
operator<<(ostream& os, const RRClass& rrclass) {
os << rrclass.toText();
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
index 4b12222..153de04 100644
--- a/src/lib/dns/rrcollator.cc
+++ b/src/lib/dns/rrcollator.cc
@@ -42,7 +42,7 @@ public:
const RdataPtr& rdata);
RRsetPtr current_rrset_;
- AddRRsetCallback callback_;
+ const AddRRsetCallback callback_;
};
namespace {
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 16ec23c..5960759 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -421,14 +421,15 @@ removeParam(uint16_t code, MC& codemap, MS& stringmap) {
return (false);
}
-template <typename PT, typename MS, typename ET>
-inline uint16_t
-textToCode(const string& code_str, MS& stringmap) {
+template <typename PT, typename MS>
+inline bool
+textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
typename MS::const_iterator found;
found = stringmap.find(code_str);
if (found != stringmap.end()) {
- return (found->second->code_);
+ ret_code = found->second->code_;
+ return (true);
}
size_t l = code_str.size();
@@ -441,10 +442,12 @@ textToCode(const string& code_str, MS& stringmap) {
l - PT::UNKNOWN_PREFIXLEN()));
iss >> dec >> code;
if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
- return (code);
+ ret_code = code;
+ return (true);
}
}
- isc_throw(ET, "Unrecognized RR parameter string: " + code_str);
+
+ return (false);
}
template <typename PT, typename MC>
@@ -475,10 +478,12 @@ RRParamRegistry::removeType(uint16_t code) {
impl_->str2typemap));
}
-uint16_t
-RRParamRegistry::textToTypeCode(const string& type_string) const {
- return (textToCode<RRTypeParam, StrRRTypeMap,
- InvalidRRType>(type_string, impl_->str2typemap));
+bool
+RRParamRegistry::textToTypeCode(const string& type_string,
+ uint16_t& type_code) const
+{
+ return (textToCode<RRTypeParam, StrRRTypeMap>
+ (type_string, impl_->str2typemap, type_code));
}
string
@@ -499,10 +504,12 @@ RRParamRegistry::removeClass(uint16_t code) {
impl_->str2classmap));
}
-uint16_t
-RRParamRegistry::textToClassCode(const string& class_string) const {
- return (textToCode<RRClassParam, StrRRClassMap,
- InvalidRRClass>(class_string, impl_->str2classmap));
+bool
+RRParamRegistry::textToClassCode(const string& class_string,
+ uint16_t& class_code) const
+{
+ return (textToCode<RRClassParam, StrRRClassMap>
+ (class_string, impl_->str2classmap, class_code));
}
string
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index 56ae981..bf86436 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -384,16 +384,22 @@ public:
/// \brief Convert a textual representation of an RR type to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR type to the corresponding integer
- /// code. If a mapping is found, it returns the associated type code;
- /// otherwise, if the given string is in the form of "TYPEnnnn", it returns
- /// the corresponding number as the type code; otherwise, it throws an
- /// exception of class \c InvalidRRType.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR type to the corresponding
+ /// integer code. If a mapping is found, it returns true with the
+ /// associated type code in \c type_code; otherwise, if the given
+ /// string is in the form of "TYPEnnnn", it returns true with the
+ /// corresponding number as the type code in \c type_code;
+ /// otherwise, it returns false and \c type_code is untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param type_string The textual representation of the RR type.
- /// \return The RR type code for \c type_string.
- uint16_t textToTypeCode(const std::string& type_string) const;
+ /// \param type_code Returns the RR type code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToTypeCode(const std::string& type_string,
+ uint16_t& type_code) const;
/// \brief Convert type code into its textual representation.
///
@@ -415,16 +421,23 @@ public:
/// \brief Convert a textual representation of an RR class to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR class to the corresponding integer
- /// code. If a mapping is found, it returns the associated class code;
- /// otherwise, if the given string is in the form of "CLASSnnnn", it returns
- /// the corresponding number as the class code; otherwise, it throws an
- /// exception of class \c InvalidRRClass.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR class to the
+ /// corresponding integer code. If a mapping is found, it returns
+ /// true with the associated class code in \c class_code; otherwise,
+ /// if the given string is in the form of "CLASSnnnn", it returns
+ /// true with the corresponding number as the class code in
+ /// \c class_code; otherwise, it returns false and \c class_code is
+ /// untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param class_string The textual representation of the RR class.
- /// \return The RR class code for \c class_string.
- uint16_t textToClassCode(const std::string& class_string) const;
+ /// \param class_code Returns the RR class code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToClassCode(const std::string& class_string,
+ uint16_t& class_code) const;
/// \brief Convert class code into its textual representation.
///
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
index 2da03d9..5ae172a 100644
--- a/src/lib/dns/rrset_collection_base.h
+++ b/src/lib/dns/rrset_collection_base.h
@@ -96,6 +96,8 @@ protected:
/// iterator only.
class Iter {
public:
+ virtual ~Iter() {};
+
/// \brief Returns the \c AbstractRRset currently pointed to by
/// the iterator.
virtual const isc::dns::AbstractRRset& getValue() = 0;
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index 7904d84..23d57f4 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -133,7 +133,7 @@ public:
/// One main purpose of this function is to minimize the overhead
/// when the given text does not represent a valid RR TTL. For this
/// reason this function intentionally omits the capability of delivering
- /// details reason for the parse failure, such as in the \c want()
+ /// a detailed reason for the parse failure, such as in the \c want()
/// string when exception is thrown from the constructor (it will
/// internally require a creation of string object, which is relatively
/// expensive). If such detailed information is necessary, the constructor
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index 4ef4e67..84892bf 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -31,8 +31,11 @@ using isc::dns::RRType;
namespace isc {
namespace dns {
-RRType::RRType(const std::string& typestr) {
- typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
+RRType::RRType(const std::string& type_str) {
+ if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode_)) {
+ isc_throw(InvalidRRType,
+ "Unrecognized RR type string: " + type_str);
+ }
}
RRType::RRType(InputBuffer& buffer) {
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index d71ec13..5029681 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -73,9 +73,9 @@ run_unittests_SOURCES += tsig_unittest.cc
run_unittests_SOURCES += tsigerror_unittest.cc
run_unittests_SOURCES += tsigkey_unittest.cc
run_unittests_SOURCES += tsigrecord_unittest.cc
-run_unittests_SOURCES += character_string_unittest.cc
run_unittests_SOURCES += master_loader_callbacks_test.cc
run_unittests_SOURCES += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
# We shouldn't need to include BOTAN_LIBS here, but there
diff --git a/src/lib/dns/tests/character_string_unittest.cc b/src/lib/dns/tests/character_string_unittest.cc
deleted file mode 100644
index e8f5884..0000000
--- a/src/lib/dns/tests/character_string_unittest.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#include <gtest/gtest.h>
-
-#include <dns/rdata.h>
-#include <dns/tests/unittest_util.h>
-#include <dns/character_string.h>
-
-using isc::UnitTestUtil;
-
-using namespace std;
-using namespace isc;
-using namespace isc::dns;
-using namespace isc::dns::characterstr;
-using namespace isc::dns::rdata;
-
-namespace {
-
-class CharacterString {
-public:
- CharacterString(const string& str){
- string::const_iterator it = str.begin();
- characterStr_ = getNextCharacterString(str, it, &is_quoted_);
- }
- const string& str() const { return characterStr_; }
- bool quoted() const { return (is_quoted_); }
-private:
- string characterStr_;
- bool is_quoted_;
-};
-
-TEST(CharacterStringTest, testNormalCase) {
- CharacterString cstr1("foo");
- EXPECT_EQ(string("foo"), cstr1.str());
-
- // Test <character-string> that separated by space
- CharacterString cstr2("foo bar");
- EXPECT_EQ(string("foo"), cstr2.str());
- EXPECT_FALSE(cstr2.quoted());
-
- // Test <character-string> that separated by quotes
- CharacterString cstr3("\"foo bar\"");
- EXPECT_EQ(string("foo bar"), cstr3.str());
- EXPECT_TRUE(cstr3.quoted());
-
- // Test <character-string> that not separate by quotes but ended with quotes
- CharacterString cstr4("foo\"");
- EXPECT_EQ(string("foo\""), cstr4.str());
- EXPECT_FALSE(cstr4.quoted());
-}
-
-TEST(CharacterStringTest, testBadCase) {
- // The <character-string> that started with quotes should also be ended
- // with quotes
- EXPECT_THROW(CharacterString cstr("\"foo"), InvalidRdataText);
-
- // The string length cannot exceed 255 characters
- string str;
- for (int i = 0; i < 257; ++i) {
- str += 'A';
- }
- EXPECT_THROW(CharacterString cstr(str), CharStringTooLong);
-}
-
-TEST(CharacterStringTest, testEscapeCharacter) {
- CharacterString cstr1("foo\\bar");
- EXPECT_EQ(string("foobar"), cstr1.str());
-
- CharacterString cstr2("foo\\\\bar");
- EXPECT_EQ(string("foo\\bar"), cstr2.str());
-
- CharacterString cstr3("fo\\111bar");
- EXPECT_EQ(string("foobar"), cstr3.str());
-
- CharacterString cstr4("fo\\1112bar");
- EXPECT_EQ(string("foo2bar"), cstr4.str());
-
- // There must be at least 3 digits followed by '\'
- EXPECT_THROW(CharacterString cstr("foo\\98ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\9ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\98"), InvalidRdataText);
-}
-
-} // namespace
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
index db0cbec..a26b02c 100644
--- a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -29,10 +30,13 @@ using namespace isc::dns::master_lexer_internal;
namespace {
+const char* const test_input =
+ "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n";
+
class InputSourceTest : public ::testing::Test {
protected:
InputSourceTest() :
- str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+ str_(test_input),
str_length_(strlen(str_)),
iss_(str_),
source_(iss_)
@@ -73,6 +77,7 @@ checkGetAndUngetChar(InputSource& source,
{
for (size_t i = 0; i < str_length; ++i) {
EXPECT_EQ(str[i], source.getChar());
+ EXPECT_EQ(i + 1, source.getPosition());
EXPECT_FALSE(source.atEOF());
}
@@ -85,6 +90,10 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be set.
EXPECT_TRUE(source.atEOF());
+ // It doesn't increase the position count.
+ EXPECT_EQ(str_length, source.getPosition());
+ EXPECT_EQ(str_length, source.getSize()); // this should be == getSize().
+
// Now, let's go backwards. This should cause the EOF to be set to
// false.
source.ungetChar();
@@ -92,6 +101,9 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be false.
EXPECT_FALSE(source.atEOF());
+ // But the position shouldn't change.
+ EXPECT_EQ(str_length, source.getPosition());
+
// This should cause EOF to be set again.
EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
@@ -106,6 +118,7 @@ checkGetAndUngetChar(InputSource& source,
// Skip one character.
source.ungetChar();
EXPECT_EQ(str[index], source.getChar());
+ EXPECT_EQ(index + 1, source.getPosition());
// Skip the character we received again.
source.ungetChar();
}
@@ -144,6 +157,7 @@ TEST_F(InputSourceTest, ungetAll) {
// Now we are back to where we started.
EXPECT_EQ(1, source_.getCurrentLine());
EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(0, source_.getPosition());
}
TEST_F(InputSourceTest, compact) {
@@ -175,6 +189,9 @@ TEST_F(InputSourceTest, compact) {
EXPECT_TRUE(source_.atEOF());
EXPECT_EQ(4, source_.getCurrentLine());
+ // compact shouldn't change the position count.
+ EXPECT_EQ(source_.getSize(), source_.getPosition());
+
// Skip the EOF.
source_.ungetChar();
@@ -322,4 +339,36 @@ TEST_F(InputSourceTest, saveLine) {
EXPECT_FALSE(source_.atEOF());
}
+TEST_F(InputSourceTest, getSize) {
+ // A simple case using string stream
+ EXPECT_EQ(strlen(test_input), source_.getSize());
+
+ // Check it works with an empty input
+ istringstream iss("");
+ EXPECT_EQ(0, InputSource(iss).getSize());
+
+ // Pretend there's an error in seeking in the stream. It will be
+ // considered a seek specific error, and getSize() returns "unknown".
+ iss.setstate(std::ios_base::failbit);
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize());
+ // The fail bit should have been cleared.
+ EXPECT_FALSE(iss.fail());
+
+ // Pretend there's a *critical* error in the stream. The constructor will
+ // throw in the attempt of getting the input size.
+ iss.setstate(std::ios_base::badbit);
+ EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError);
+
+ // Check with input source from file name. We hardcode the file size
+ // for simplicity. It won't change too often.
+ EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize());
+}
+
+TEST_F(InputSourceTest, getPosition) {
+ // Initially the position is set to 0. Other cases are tested in tests
+ // for get and unget.
+ EXPECT_EQ(0, source_.getPosition());
+ EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition());
+}
+
} // end namespace
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 62ad79d..c8ab61f 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -52,6 +52,8 @@ void
checkEmptySource(const MasterLexer& lexer) {
EXPECT_TRUE(lexer.getSourceName().empty());
EXPECT_EQ(0, lexer.getSourceLine());
+ EXPECT_EQ(0, lexer.getTotalSourceSize());
+ EXPECT_EQ(0, lexer.getPosition());
}
TEST_F(MasterLexerTest, preOpen) {
@@ -61,9 +63,11 @@ TEST_F(MasterLexerTest, preOpen) {
TEST_F(MasterLexerTest, pushStream) {
EXPECT_EQ(0, lexer.getSourceCount());
+ ss << "test";
lexer.pushSource(ss);
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
EXPECT_EQ(1, lexer.getSourceCount());
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test")
// From the point of view of this test, we only have to check (though
// indirectly) getSourceLine calls InputSource::getCurrentLine. It should
@@ -85,6 +89,10 @@ TEST_F(MasterLexerTest, pushFile) {
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
EXPECT_EQ(1, lexer.getSourceLine());
+ // 143 = size of the test zone file. hardcode it assuming it won't change
+ // too often.
+ EXPECT_EQ(143, lexer.getTotalSourceSize());
+
lexer.popSource();
checkEmptySource(lexer);
EXPECT_EQ(0, lexer.getSourceCount());
@@ -116,21 +124,42 @@ TEST_F(MasterLexerTest, pushFileFail) {
}
TEST_F(MasterLexerTest, nestedPush) {
+ ss << "test";
lexer.pushSource(ss);
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
// We can push another source without popping the previous one.
lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(143 + 4, lexer.getTotalSourceSize()); // see above for magic nums
// popSource() works on the "topmost" (last-pushed) source
lexer.popSource();
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
lexer.popSource();
EXPECT_TRUE(lexer.getSourceName().empty());
}
+TEST_F(MasterLexerTest, unknownSourceSize) {
+ // Similar to the previous case, but the size of the second source
+ // will be considered "unknown" (by emulating an error).
+ ss << "test";
+ lexer.pushSource(ss);
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+ stringstream ss2;
+ ss2.setstate(std::ios_base::failbit); // this will make the size unknown
+ lexer.pushSource(ss2);
+ // Then the total size is also unknown.
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+
+ // If we pop that source, the size becomes known again.
+ lexer.popSource();
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
+}
+
TEST_F(MasterLexerTest, invalidPop) {
// popSource() cannot be called if the sources stack is empty.
EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
@@ -141,25 +170,31 @@ TEST_F(MasterLexerTest, noSource) {
EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
}
-// Test getting some tokens
+// Test getting some tokens. It also check basic behavior of getPosition().
TEST_F(MasterLexerTest, getNextToken) {
ss << "\n \n\"STRING\"\n";
lexer.pushSource(ss);
// First, the newline should get out.
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Then the whitespace, if we specify the option.
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition());
// The newline
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n
// The (quoted) string
EXPECT_EQ(MasterToken::QSTRING,
lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes
// And the end of line and file
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n
EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change
}
// Test we correctly find end of file.
@@ -204,20 +239,25 @@ TEST_F(MasterLexerTest, getUnbalancedString) {
EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
}
-// Test ungetting tokens works
+// Test ungetting tokens works. Also check getPosition() is adjusted
TEST_F(MasterLexerTest, ungetToken) {
ss << "\n (\"string\"\n) more";
lexer.pushSource(ss);
// Try getting the newline
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Return it and get again
lexer.ungetToken();
+ EXPECT_EQ(0, lexer.getPosition());
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Get the string and return it back
EXPECT_EQ(MasterToken::QSTRING,
lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition());
lexer.ungetToken();
+ EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n
// But if we change the options, it honors them
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::QSTRING |
@@ -253,7 +293,8 @@ TEST_F(MasterLexerTest, ungetRealOptions) {
}
// Check the initial whitespace is found even in the first line of included
-// file
+// file. It also confirms getPosition() works for multiple sources, each
+// of which is partially parsed.
TEST_F(MasterLexerTest, includeAndInitialWS) {
ss << " \n";
lexer.pushSource(ss);
@@ -263,9 +304,11 @@ TEST_F(MasterLexerTest, includeAndInitialWS) {
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(1, lexer.getPosition());
lexer.pushSource(ss2);
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition()); // should be sum of position positions.
}
// Test only one token can be ungotten
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 89a1440..051c662 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -391,7 +391,8 @@ struct ErrorCase {
"Missing Rdata" },
{ "www 3600 IN", NULL, "Unexpected EOLN" },
- { "www 3600 CH TXT nothing", NULL, "Class mismatch" },
+ { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN",
+ "Class mismatch" },
{ "www \"3600\" IN A 192.0.2.1", NULL, "Quoted TTL" },
{ "www 3600 \"IN\" A 192.0.2.1", NULL, "Quoted class" },
{ "www 3600 IN \"A\" 192.0.2.1", NULL, "Quoted type" },
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
index 9d23622..3059669 100644
--- a/src/lib/dns/tests/rdata_char_string_unittest.cc
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -14,8 +14,10 @@
#include <util/unittests/wiredata.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -25,7 +27,10 @@
using namespace isc::dns;
using namespace isc::dns::rdata;
using isc::dns::rdata::generic::detail::CharString;
-using isc::dns::rdata::generic::detail::strToCharString;
+using isc::dns::rdata::generic::detail::bufferToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
+using isc::dns::rdata::generic::detail::compareCharStrings;
using isc::util::unittests::matchWireData;
namespace {
@@ -60,19 +65,19 @@ createStringRegion(const std::string& str) {
TEST_F(CharStringTest, normalConversion) {
uint8_t tmp[3]; // placeholder for expected sequence
- strToCharString(str_region, chstr);
+ stringToCharString(str_region, chstr);
matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
// Empty string
chstr.clear();
- strToCharString(createStringRegion(""), chstr);
+ stringToCharString(createStringRegion(""), chstr);
tmp[0] = 0;
matchWireData(tmp, 1, &chstr[0], chstr.size());
// Possible largest char string
chstr.clear();
std::string long_str(255, 'x');
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
std::vector<uint8_t> expected;
expected.push_back(255); // len of char string
expected.insert(expected.end(), long_str.begin(), long_str.end());
@@ -83,32 +88,32 @@ TEST_F(CharStringTest, normalConversion) {
chstr.clear();
long_str.at(254) = '\\'; // replace the last 'x' with '\'
long_str.append("120"); // 'x' = 120
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
// Escaped '\'
chstr.clear();
tmp[0] = 1;
tmp[1] = '\\';
- strToCharString(createStringRegion("\\\\"), chstr);
+ stringToCharString(createStringRegion("\\\\"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Boundary values for \DDD
chstr.clear();
tmp[0] = 1;
tmp[1] = 0;
- strToCharString(createStringRegion("\\000"), chstr);
+ stringToCharString(createStringRegion("\\000"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
chstr.clear();
- strToCharString(createStringRegion("\\255"), chstr);
+ stringToCharString(createStringRegion("\\255"), chstr);
tmp[0] = 1;
tmp[1] = 255;
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Another digit follows DDD; it shouldn't cause confusion
chstr.clear();
- strToCharString(createStringRegion("\\2550"), chstr);
+ stringToCharString(createStringRegion("\\2550"), chstr);
tmp[0] = 2; // string len is now 2
tmp[2] = '0';
matchWireData(tmp, 3, &chstr[0], chstr.size());
@@ -116,13 +121,13 @@ TEST_F(CharStringTest, normalConversion) {
TEST_F(CharStringTest, badConversion) {
// string cannot exceed 255 bytes
- EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
- chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+ chstr),
CharStringTooLong);
// input string ending with (non escaped) '\'
chstr.clear();
- EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
InvalidRdataText);
}
@@ -130,18 +135,118 @@ TEST_F(CharStringTest, badDDD) {
// Check various type of bad form of \DDD
// Not a number
- EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
InvalidRdataText);
- EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
InvalidRdataText);
// Not in the range of uint8_t
- EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
InvalidRdataText);
// Short buffer
- EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
InvalidRdataText);
}
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ assert(sizeof(idata) > length);
+ idata[0] = static_cast<uint8_t>(length);
+ std::memcpy(idata + 1, cur->data, length);
+ const CharString test_data(idata, idata + length + 1);
+ EXPECT_EQ(cur->expected, charStringToString(test_data));
+ }
+}
+
+TEST_F(CharStringTest, bufferToCharString) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ size_t read = bufferToCharString(buf, chstr_size, chstr);
+
+ EXPECT_EQ(chstr_size, read);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+}
+
+TEST_F(CharStringTest, bufferToCharString_bad) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ // Set valid data in both so we can make sure the charstr is not
+ // modified
+ bufferToCharString(buf, chstr_size, chstr);
+ ASSERT_EQ("Test String", charStringToString(chstr));
+
+ // Should be at end of buffer now, so it should fail
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // reset and try to read with too low rdata_len
+ buf.setPosition(0);
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // set internal charstring len too high
+ const uint8_t test_charstr_err[] = {
+ sizeof("Test String") + 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+ };
+ buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err));
+ EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+}
+
+
+
+TEST_F(CharStringTest, compareCharString) {
+ CharString charstr;
+ CharString charstr2;
+ CharString charstr_small1;
+ CharString charstr_small2;
+ CharString charstr_large1;
+ CharString charstr_large2;
+ CharString charstr_empty;
+
+ stringToCharString(createStringRegion("test string"), charstr);
+ stringToCharString(createStringRegion("test string"), charstr2);
+ stringToCharString(createStringRegion("test strin"), charstr_small1);
+ stringToCharString(createStringRegion("test strina"), charstr_small2);
+ stringToCharString(createStringRegion("test stringa"), charstr_large1);
+ stringToCharString(createStringRegion("test strinz"), charstr_large2);
+
+ EXPECT_EQ(0, compareCharStrings(charstr, charstr2));
+ EXPECT_EQ(0, compareCharStrings(charstr2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr));
+
+ EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty));
+ EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty));
+}
+
} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index f592066..7be2cb6 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -51,10 +51,9 @@ TEST_F(Rdata_HINFO_Test, createFromText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(string("Pentium"), hinfo.getCPU());
EXPECT_EQ(string("Linux"), hinfo.getOS());
-
// Test the text with double quotes in the middle of string
HINFO hinfo1(hinfo_str1);
- EXPECT_EQ(string("Pen\"tium"), hinfo1.getCPU());
+ EXPECT_EQ(string("Pen\\\"tium"), hinfo1.getCPU());
}
TEST_F(Rdata_HINFO_Test, badText) {
@@ -96,11 +95,20 @@ TEST_F(Rdata_HINFO_Test, createFromLexer) {
EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
"\"Pentium\" \"Linux\" "
"\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\""));
}
TEST_F(Rdata_HINFO_Test, toText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(hinfo_str, hinfo.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("\"a\" \"b\"", HINFO("a b").toText());
+ // will not add additional quotes
+ EXPECT_EQ("\"a\" \"b\"", HINFO("\"a\" \"b\"").toText());
+ // And make sure escaped quotes and spaces are left intact
+ EXPECT_EQ("\"a\\\"\" \"b c\"", HINFO("\"a\\\"\" \"b c\"").toText());
}
TEST_F(Rdata_HINFO_Test, toWire) {
@@ -135,4 +143,17 @@ TEST_F(Rdata_HINFO_Test, compare) {
EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large2)));
}
+// Copy/assign test
+TEST_F(Rdata_HINFO_Test, copy) {
+ HINFO hinfo(hinfo_str);
+ HINFO hinfo2(hinfo);
+ HINFO hinfo3 = hinfo;
+
+ EXPECT_EQ(0, hinfo.compare(hinfo2));
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+
+ hinfo3 = hinfo;
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index fed98cb..d828e73 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -99,10 +99,12 @@ TEST_F(Rdata_NAPTR_Test, badText) {
// Order or preference cannot be missed
EXPECT_THROW(const NAPTR naptr("10 \"S\" SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- // Fields must be seperated by spaces
+ // Unquoted fields must be seperated by spaces
EXPECT_THROW(const NAPTR naptr("100 10S SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- EXPECT_THROW(const NAPTR naptr("100 10 \"S\"\"SIP\" \"\" _sip._udp.example.com."),
+ EXPECT_THROW(const NAPTR naptr("10010 \"S\" \"SIP\" \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(const NAPTR naptr("100 10 SSIP \"\" _sip._udp.example.com."),
InvalidRdataText);
// Field cannot be missing
EXPECT_THROW(const NAPTR naptr("100 10 \"S\""), InvalidRdataText);
@@ -128,6 +130,26 @@ TEST_F(Rdata_NAPTR_Test, createFromWire) {
EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
}
+TEST_F(Rdata_NAPTR_Test, createFromWireTooLongDataLen) {
+ static uint8_t naptr_rdata_long[] = {
+ 0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,0x50,0x2b,0x44,0x32,0x55,
+ 0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,0x70,0x07,0x65,0x78,
+ 0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00,0xff,0xff,0xff,0xff};
+ InputBuffer input_buffer(naptr_rdata_long, sizeof(naptr_rdata_long));
+ EXPECT_THROW(NAPTR naptr(input_buffer, sizeof(naptr_rdata_long)),
+ isc::dns::DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooShortDataLen) {
+ // missing data (just set rdata_len too low)
+ for (size_t i = 0; i < sizeof(naptr_rdata); ++i) {
+ // Just use existing correct buffer but set rdata_len too low
+ InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+ EXPECT_THROW(NAPTR naptr(input_buffer, i),
+ isc::dns::DNSMessageFORMERR);
+ }
+}
+
TEST_F(Rdata_NAPTR_Test, createFromLexer) {
const NAPTR rdata_naptr(naptr_str);
@@ -161,6 +183,14 @@ TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
TEST_F(Rdata_NAPTR_Test, toText) {
NAPTR naptr(naptr_str);
EXPECT_EQ(naptr_str, naptr.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 S SIP+D2U .* _sip._udp.example.com.").toText());
+ // will not add additional quotes
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.")
+ .toText());
}
TEST_F(Rdata_NAPTR_Test, compare) {
@@ -177,16 +207,38 @@ TEST_F(Rdata_NAPTR_Test, compare) {
NAPTR naptr_large5(naptr_str_large5);
EXPECT_EQ(0, naptr.compare(NAPTR(naptr_str)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small1)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small2)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small3)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small4)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small5)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large1)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large2)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large3)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large4)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large5)));
+ EXPECT_EQ(1, naptr.compare(naptr_small1));
+ EXPECT_EQ(1, naptr.compare(naptr_small2));
+ EXPECT_EQ(1, naptr.compare(naptr_small3));
+ EXPECT_EQ(1, naptr.compare(naptr_small4));
+ EXPECT_EQ(1, naptr.compare(naptr_small5));
+ EXPECT_EQ(-1, naptr.compare(naptr_large1));
+ EXPECT_EQ(-1, naptr.compare(naptr_large2));
+ EXPECT_EQ(-1, naptr.compare(naptr_large3));
+ EXPECT_EQ(-1, naptr.compare(naptr_large4));
+ EXPECT_EQ(-1, naptr.compare(naptr_large5));
+ EXPECT_EQ(-1, naptr_small1.compare(naptr));
+ EXPECT_EQ(-1, naptr_small2.compare(naptr));
+ EXPECT_EQ(-1, naptr_small3.compare(naptr));
+ EXPECT_EQ(-1, naptr_small4.compare(naptr));
+ EXPECT_EQ(-1, naptr_small5.compare(naptr));
+ EXPECT_EQ(1, naptr_large1.compare(naptr));
+ EXPECT_EQ(1, naptr_large2.compare(naptr));
+ EXPECT_EQ(1, naptr_large3.compare(naptr));
+ EXPECT_EQ(1, naptr_large4.compare(naptr));
+ EXPECT_EQ(1, naptr_large5.compare(naptr));
+}
+
+TEST_F(Rdata_NAPTR_Test, copy) {
+ NAPTR naptr(naptr_str);
+ NAPTR naptr2(naptr);
+ NAPTR naptr3 = naptr;
+
+ EXPECT_EQ(0, naptr.compare(naptr2));
+ EXPECT_EQ(0, naptr.compare(naptr3));
+
+ naptr3 = naptr;
+ EXPECT_EQ(0, naptr.compare(naptr3));
}
}
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 52f0601..ed1c29f 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -33,15 +33,129 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_SOA_Test : public RdataTest {
protected:
- Rdata_SOA_Test() : rdata_soa(Name("ns.example.com"),
- Name("root.example.com"),
- 2010012601, 3600, 300, 3600000, 1200)
+ Rdata_SOA_Test() :
+ rdata_soa(Name("ns.example.com"),
+ Name("root.example.com"),
+ 2010012601, 3600, 300, 3600000, 1200)
{}
+
+ // Common check to see if the given text can be used to construct SOA
+ // Rdata that is identical rdata_soa.
+ void checkFromText(const char* soa_txt, const Name* origin = NULL) {
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ if (origin == NULL) {
+ // from-string constructor works correctly only when origin
+ // is NULL (by its nature).
+ EXPECT_EQ(0, generic::SOA(soa_txt).compare(rdata_soa));
+ }
+ EXPECT_EQ(0, generic::SOA(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb).compare(rdata_soa));
+ }
+
+ // Common check if given text (which is invalid as SOA RDATA) is rejected
+ // with the specified type of exception: ExForString is the expected
+ // exception for the "from string" constructor; ExForLexer is for the
+ // constructor with master lexer.
+ template <typename ExForString, typename ExForLexer>
+ void checkFromBadTexxt(const char* soa_txt, const Name* origin = NULL) {
+ EXPECT_THROW(generic::SOA soa(soa_txt), ExForString);
+
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+ EXPECT_THROW(generic::SOA soa(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb), ExForLexer);
+ }
+
const generic::SOA rdata_soa;
};
TEST_F(Rdata_SOA_Test, createFromText) {
- //TBD
+ // A simple case.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200");
+
+ // Beginning and trailing space are ignored.
+ checkFromText(" ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200 ");
+
+ // using extended TTL-like form for some parameters.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 1H 5M 1000H 20M");
+
+ // multi-line.
+ checkFromText("ns.example.com. (root.example.com.\n"
+ "2010012601 1H 5M 1000H) 20M");
+
+ // relative names for MNAME and RNAME with a separate origin (lexer
+ // version only)
+ const Name origin("example.com");
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M", &origin);
+
+ // with the '@' notation with a separate origin (lexer version only)
+ const Name full_mname("ns.example.com");
+ checkFromText("@ root.example.com. 2010012601 1H 5M 1000H 20M",
+ &full_mname);
+
+ // bad MNAME/RNAMEs
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ "bad..example. . 2010012601 1H 5M 1000H 20M");
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ ". bad..example. 2010012601 1H 5M 1000H 20M");
+
+ // Names shouldn't be quoted. (Note: on completion of #2534, the resulting
+ // exception will be different).
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ "\".\" . 0 0 0 0 0");
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ ". \".\" 0 0 0 0 0");
+
+ // Missing MAME or RNAME: for the string version, the serial would be
+ // tried as RNAME and result in "not absolute". For the lexer version,
+ // it reaches the end-of-line, missing min TTL.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". 2010012601 0 0 0 0", &Name::ROOT_NAME());
+
+ // bad serial. the string version converts lexer error to
+ // InvalidRdataText.
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . bad 0 0 0 0");
+
+ // bad serial; exceeding the uint32_t range (4294967296 = 2^32)
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . 4294967296 0 0 0 0");
+
+ // Bad format for other numeric parameters. These will be tried as a TTL,
+ // and result in an exception there.
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 bad 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 4294967296 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 bad 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 4294967296 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 bad 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 4294967296 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 0 bad");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 0 4294967296");
+
+ // No space between RNAME and serial. This case is the same as missing
+ // M/RNAME.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". example.0 0 0 0 0", &Name::ROOT_NAME());
+
+ // Extra parameter. string version immediately detects the error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0 extra"), InvalidRdataText);
+ // Likewise. Redundant newline is also considered an error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0\n"), InvalidRdataText);
+ EXPECT_THROW(generic::SOA soa("\n. . 0 0 0 0 0"), InvalidRdataText);
+ // lexer version defers the check to the upper layer (we pass origin
+ // to skip the check with the string version).
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M extra", &origin);
}
TEST_F(Rdata_SOA_Test, createFromWire) {
@@ -97,4 +211,42 @@ TEST_F(Rdata_SOA_Test, getMinimum) {
0, 0, 0, 0, 0x80706050).getMinimum());
}
+void
+compareCheck(const generic::SOA& small, const generic::SOA& large) {
+ EXPECT_GT(0, small.compare(large));
+ EXPECT_LT(0, large.compare(small));
+}
+
+TEST_F(Rdata_SOA_Test, compare) {
+ // Check simple equivalence
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200")));
+ // Check name comparison is case insensitive
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "NS.example.com. root.EXAMPLE.com. "
+ "2010012601 3600 300 3600000 1200")));
+
+ // Check names are compared in the RDATA comparison semantics (different
+ // from DNSSEC ordering for owner names)
+ compareCheck(generic::SOA("a.example. . 0 0 0 0 0"),
+ generic::SOA("example. . 0 0 0 0 0"));
+ compareCheck(generic::SOA(". a.example. 0 0 0 0 0"),
+ generic::SOA(". example. 0 0 0 0 0"));
+
+ // Compare other numeric fields: 1076895760 = 0x40302010,
+ // 270544960 = 0x10203040. These are chosen to make sure that machine
+ // endian doesn't confuse the comparison results.
+ compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
+ generic::SOA(". . 1076895760 0 0 0 0"));
+ compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
+ generic::SOA(". . 0 1076895760 0 0 0"));
+ compareCheck(generic::SOA(". . 0 0 270544960 0 0"),
+ generic::SOA(". . 0 0 1076895760 0 0"));
+ compareCheck(generic::SOA(". . 0 0 0 270544960 0"),
+ generic::SOA(". . 0 0 0 1076895760 0"));
+ compareCheck(generic::SOA(". . 0 0 0 0 270544960"),
+ generic::SOA(". . 0 0 0 0 1076895760"));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index d045875..cb3c44d 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -57,7 +57,6 @@ template<class TXT_LIKE>
class Rdata_TXT_LIKE_Test : public RdataTest {
protected:
Rdata_TXT_LIKE_Test() :
- loader_cb(MasterLoaderCallbacks::getNullCallbacks()),
wiredata_longesttxt(256, 'a'),
rdata_txt_like("Test-String"),
rdata_txt_like_empty("\"\""),
@@ -67,7 +66,6 @@ protected:
}
protected:
- MasterLoaderCallbacks loader_cb;
vector<uint8_t> wiredata_longesttxt;
const TXT_LIKE rdata_txt_like;
const TXT_LIKE rdata_txt_like_empty;
@@ -334,6 +332,24 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+ // Check escape behavior
+ const TypeParam double_quotes("Test-String\"Test-String\"");
+ EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+ const TypeParam semicolon("Test-String\\;Test-String");
+ EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+ const TypeParam backslash("Test-String\\\\Test-String");
+ EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+ const TypeParam before_x20("Test-String\\031Test-String");
+ EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+ const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+ const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+ const TypeParam after_x7e("Test-String\\127Test-String");
+ EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
}
TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 95f50e7..67eae2f 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -41,7 +41,8 @@ namespace isc {
namespace dns {
namespace rdata {
RdataTest::RdataTest() :
- obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+ loader_cb(MasterLoaderCallbacks::getNullCallbacks())
{}
RdataPtr
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index af19311..3f1dbb3 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -42,6 +42,7 @@ protected:
/// used to test the compare() method against a well-known RR type.
RdataPtr rdata_nomatch;
MasterLexer lexer;
+ MasterLoaderCallbacks loader_cb;
};
namespace test {
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 6156be5..11f1c54 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -59,7 +59,7 @@ RRClassTest::rrclassFactoryFromWire(const char* datafile) {
return (RRClass(buffer));
}
-TEST_F(RRClassTest, fromText) {
+TEST_F(RRClassTest, fromTextConstructor) {
EXPECT_EQ("IN", RRClass("IN").toText());
EXPECT_EQ("CH", RRClass("CH").toText());
@@ -96,6 +96,14 @@ TEST_F(RRClassTest, toText) {
EXPECT_EQ("CLASS65000", RRClass(65000).toText());
}
+TEST_F(RRClassTest, createFromText) {
+ const MaybeRRClass rrclass("IN");
+ EXPECT_TRUE(rrclass);
+ EXPECT_EQ("IN", rrclass->toText());
+ EXPECT_TRUE(RRClass::createFromText("CH"));
+ EXPECT_FALSE(RRClass::createFromText("ZZ"));
+}
+
TEST_F(RRClassTest, toWireBuffer) {
rrclass_1.toWire(obuffer);
rrclass_0x80.toWire(obuffer);
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
new file mode 100644
index 0000000..dbe204d
--- /dev/null
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const char* const soa_txt = "ns.example.com. root.example.com. 0 0 0 0 0";
+const char* const ns_txt1 = "ns.example.com.";
+const char* const ns_a_txt1 = "192.0.2.1";
+const char* const ns_txt2 = "ns2.example.com.";
+const char* const ns_a_txt2 = "192.0.2.2";
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+ ZoneCheckerTest() :
+ zname_("example.com"), zclass_(RRClass::IN()),
+ soa_(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60))),
+ ns_(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60))),
+ callbacks_(boost::bind(&ZoneCheckerTest::callback, this, _1, true),
+ boost::bind(&ZoneCheckerTest::callback, this, _1, false))
+ {
+ std::stringstream ss;
+ ss << "example.com. 60 IN SOA " << soa_txt << "\n";
+ ss << "example.com. 60 IN NS " << ns_txt1 << "\n";
+ ss << "ns.example.com. 60 IN A " << ns_a_txt1 << "\n";
+ ss << "ns2.example.com. 60 IN A " << ns_a_txt2 << "\n";
+ rrsets_.reset(new RRsetCollection(ss, zname_, zclass_));
+ }
+
+public:
+ // This one is passed to boost::bind. Some compilers seem to require
+ // it be public.
+ void callback(const std::string& reason, bool is_error) {
+ if (is_error) {
+ errors_.push_back(reason);
+ } else {
+ warns_.push_back(reason);
+ }
+ }
+
+protected:
+ // Check stored issue messages with expected ones. Clear vectors so
+ // the caller can check other cases.
+ void checkIssues() {
+ EXPECT_EQ(expected_errors_.size(), errors_.size());
+ for (int i = 0; i < std::min(expected_errors_.size(), errors_.size());
+ ++i) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, errors_[0].find(expected_errors_[0]))
+ << "actual message: " << errors_[0] << " expected: " <<
+ expected_errors_[0];
+ }
+ EXPECT_EQ(expected_warns_.size(), warns_.size());
+ for (int i = 0; i < std::min(expected_warns_.size(), warns_.size());
+ ++i) {
+ EXPECT_EQ(0, warns_[0].find(expected_warns_[0]))
+ << "actual message: " << warns_[0] << " expected: " <<
+ expected_warns_[0];
+ }
+
+ errors_.clear();
+ expected_errors_.clear();
+ warns_.clear();
+ expected_warns_.clear();
+ }
+
+ const Name zname_;
+ const RRClass zclass_;
+ boost::scoped_ptr<RRsetCollection> rrsets_;
+ RRsetPtr soa_;
+ RRsetPtr ns_;
+ std::vector<std::string> errors_;
+ std::vector<std::string> warns_;
+ std::vector<std::string> expected_errors_;
+ std::vector<std::string> expected_warns_;
+ ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+ // Checking a valid case. No errors or warnings should be reported.
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Multiple NS RRs are okay.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_->addRdata(generic::NS(ns_txt1));
+ ns_->addRdata(generic::NS(ns_txt2));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+ // If the zone has no SOA it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 0 SOA records");
+ checkIssues();
+
+ // If null callback is specified, checkZone() only returns the final
+ // result.
+ ZoneCheckerCallbacks noerror_callbacks(
+ NULL, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
+ checkIssues();
+
+ // If there are more than 1 SOA RR, it's also an error.
+ errors_.clear();
+ soa_->addRdata(generic::SOA(soa_txt));
+ soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+ rrsets_->addRRset(soa_);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 2 SOA records");
+ checkIssues();
+
+ // If the SOA RRset is "empty", it's treated as an implementation
+ // (rather than operational) error and results in an exception.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+ // If the zone has no NS at origin it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has no NS records");
+ checkIssues();
+
+ // Check two buggy cases like the SOA tests
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNSData) {
+ const Name ns_name("ns.example.com");
+
+ // If a ("in-bailiwick") NS name doesn't have an address record, it's
+ // reported as a warning.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::A());
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // Same check, but disabling warning callback. Same result, but without
+ // the warning.
+ ZoneCheckerCallbacks nowarn_callbacks(
+ boost::bind(&ZoneCheckerTest::callback, this, _1, true), NULL);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
+ checkIssues();
+
+ // A tricky case: if the name matches a wildcard, it should technically
+ // be considered valid, but this checker doesn't check that far and still
+ // warns.
+ RRsetPtr wild(new RRset(Name("*.example.com"), zclass_, RRType::A(),
+ RRTTL(0)));
+ wild->addRdata(in::A("192.0.2.255"));
+ rrsets_->addRRset(wild);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // If there's a CNAME at the name instead, it's an error.
+ rrsets_->removeRRset(Name("*.example.com"), zclass_, RRType::A());
+ RRsetPtr cname(new RRset(ns_name, zclass_, RRType::CNAME(), RRTTL(60)));
+ cname->addRdata(generic::CNAME("cname.example.com"));
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't have to be A. An AAAA is enough.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ RRsetPtr aaaa(new RRset(ns_name, zclass_, RRType::AAAA(), RRTTL(60)));
+ aaaa->addRdata(in::AAAA("2001:db8::1"));
+ rrsets_->addRRset(aaaa);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Coexisting CNAME makes it error (CNAME with other record is itself
+ // invalid, but it's a different issue in this context)
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't matter if the NS name is "out of bailiwick".
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Note that if the NS name is the origin name, it should be checked
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(zname_));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDelegation) {
+ // Tests various cases where there's a zone cut due to delegation between
+ // the zone origin and the NS name. In each case the NS name doesn't have
+ // an address record.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to delegation in the middle; the check for the address
+ // record should be skipped.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr child_ns(new RRset(Name("child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut at the NS name. Same result.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(ns_name, zclass_, RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut below the NS name. The check applies.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(Name("another.ns.child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDNAME) {
+ // Similar to the above case, but the zone cut is due to DNAME. This is
+ // an invalid configuration.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to DNAME at the zone origin. This is an invalid case.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr dname(new RRset(zname_, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'example.com'");
+ checkIssues();
+
+ // Zone cut due to DNAME in the middle. Same result.
+ rrsets_->removeRRset(zname_, zclass_, RRType::DNAME());
+ dname.reset(new RRset(Name("child.example.com"), zclass_, RRType::DNAME(),
+ RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'child.example.com'");
+ checkIssues();
+
+ // A tricky case: there's also an NS at the name that has DNAME. It's
+ // prohibited per RFC6672 so we could say it's "undefined". Nevertheless,
+ // this implementation prefers the NS and skips further checks.
+ ns_.reset(new RRset(Name("child.example.com"), zclass_, RRType::NS(),
+ RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut due to DNAME at the NS name. In this case DNAME doesn't
+ // affect the NS name, so it should result in "no address record" warning.
+ rrsets_->removeRRset(dname->getName(), zclass_, RRType::DNAME());
+ rrsets_->removeRRset(ns_->getName(), zclass_, RRType::NS());
+ dname.reset(new RRset(ns_name, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+}
diff --git a/src/lib/dns/zone_checker.cc b/src/lib/dns/zone_checker.cc
new file mode 100644
index 0000000..aa307d2
--- /dev/null
+++ b/src/lib/dns/zone_checker.cc
@@ -0,0 +1,196 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+
+namespace isc {
+namespace dns {
+
+namespace {
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+ return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callback) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::SOA());
+ size_t count = 0;
+ if (rrset) {
+ for (RdataIteratorPtr rit = rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next(), ++count) {
+ if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+ NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+ }
+ }
+ if (count == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+ }
+ }
+ if (count != 1) {
+ callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
+ lexical_cast<string>(count) + " SOA records");
+ }
+}
+
+// Check if a target name is beyond zone cut, either due to delegation or
+// DNAME. Note that DNAME works on the origin but not on the name itself,
+// while delegation works on the name itself (but the NS at the origin is not
+// delegation).
+ConstRRsetPtr
+findZoneCut(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
+ const unsigned int origin_count = zone_name.getLabelCount();
+ const unsigned int target_count = target_name.getLabelCount();
+ assert(origin_count <= target_count);
+
+ for (unsigned int l = origin_count; l <= target_count; ++l) {
+ const Name& mid_name = (l == target_count) ? target_name :
+ target_name.split(target_count - l);
+
+ ConstRRsetPtr found;
+ if (l != origin_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
+ NULL) {
+ return (found);
+ }
+ if (l != target_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
+ != NULL) {
+ return (found);
+ }
+ }
+ return (ConstRRsetPtr());
+}
+
+// Check if each "in-zone" NS name has an address record, identifying some
+// error cases.
+void
+checkNSNames(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
+ if (ns_rrset->getRdataCount() == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+ }
+
+ for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ const rdata::generic::NS* ns_data =
+ dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
+ if (ns_data == NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+ }
+ const Name& ns_name = ns_data->getNSName();
+ const NameComparisonResult::NameRelation reln =
+ ns_name.compare(zone_name).getRelation();
+ if (reln != NameComparisonResult::EQUAL &&
+ reln != NameComparisonResult::SUBDOMAIN) {
+ continue; // not in the zone. we can ignore it.
+ }
+
+ // Check if there's a zone cut between the origin and the NS name.
+ ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
+ zone_rrsets, ns_name);
+ if (cut_rrset) {
+ if (cut_rrset->getType() == RRType::NS()) {
+ continue; // delegation; making the NS name "out of zone".
+ } else if (cut_rrset->getType() == RRType::DNAME()) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is " +
+ "below a DNAME '" +
+ cut_rrset->getName().toText(true) +
+ "' (illegal per RFC6672)");
+ continue;
+ } else {
+ assert(false);
+ }
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is a CNAME " +
+ "(illegal per RFC2181)");
+ continue;
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
+ zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
+ callbacks.warn("zone " + zoneText(zone_name, zone_class) +
+ ": NS has no address records (A or AAAA)");
+ }
+ }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callbacks) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::NS());
+ if (rrset == NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": has no NS records");
+ return;
+ }
+ checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
+}
+
+// The following is a simple wrapper of checker callback so checkZone()
+// can also remember any critical errors.
+void
+errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
+ bool* had_error) {
+ *had_error = true;
+ callbacks->error(reason);
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks) {
+ bool had_error = false;
+ ZoneCheckerCallbacks my_callbacks(
+ boost::bind(errorWrapper, _1, &callbacks, &had_error),
+ boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
+
+ checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
+ checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
+
+ return (!had_error);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/zone_checker.h b/src/lib/dns/zone_checker.h
new file mode 100644
index 0000000..dfb4946
--- /dev/null
+++ b/src/lib/dns/zone_checker.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <boost/function.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+/// \brief Set of callbacks used in zone checks.
+///
+/// Objects of this class are expected to be passed to \c checkZone().
+class ZoneCheckerCallbacks {
+public:
+ /// \brief Functor type of the callback on some issue (error or warning).
+ ///
+ /// Its parameter indicates the reason for the corresponding issue.
+ typedef boost::function<void(const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor.
+ ///
+ /// Either or both of the callbacks can be empty, in which case the
+ /// corresponding callback will be effectively no-operation. This can be
+ /// used, for example, when the caller of \c checkZone() is only
+ /// interested in the final result. Note that a \c NULL pointer will be
+ /// implicitly converted to an empty functor object, so passing \c NULL
+ /// suffices.
+ ///
+ /// \throw none
+ ///
+ /// \param error_callback Callback functor to be called on critical errors.
+ /// \param warn_callback Callback functor to be called on non critical
+ /// issues.
+ ZoneCheckerCallbacks(const IssueCallback& error_callback,
+ const IssueCallback& warn_callback) :
+ error_callback_(error_callback), warn_callback_(warn_callback)
+ {}
+
+ /// \brief Call the callback for a critical error.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the error.
+ void error(const std::string& reason) const {
+ if (!error_callback_.empty()) {
+ error_callback_(reason);
+ }
+ }
+
+ /// \brief Call the callback for a non critical issue.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the issue.
+ void warn(const std::string& reason) const {
+ if (!warn_callback_.empty())
+ warn_callback_(reason);
+ }
+
+private:
+ IssueCallback error_callback_;
+ IssueCallback warn_callback_;
+};
+
+/// \brief Perform basic integrity checks on zone RRsets.
+///
+/// This function performs some lightweight checks on zone's SOA and (apex)
+/// NS records. Here, lightweight means it doesn't require traversing
+/// the entire zone, and should be expected to complete reasonably quickly
+/// regardless of the size of the zone.
+///
+/// It distinguishes "critical" errors and other undesirable issues:
+/// the former should be interpreted as the resulting zone shouldn't be used
+/// further, e.g, by an authoritative server implementation; the latter means
+/// the issues are better to be addressed but are not necessarily considered
+/// to make the zone invalid. Critical errors are reported via the
+/// \c error() method of \c callbacks, and non critical issues are reported
+/// via its \c warn() method.
+///
+/// Specific checks performed by this function is as follows. Failure of
+/// a check is considered a critical error unless noted otherwise:
+/// - There is exactly one SOA RR at the zone apex.
+/// - There is at least one NS RR at the zone apex.
+/// - For each apex NS record, if the NS name (the RDATA of the record) is
+/// in the zone (i.e., it's a subdomain of the zone origin and above any
+/// zone cut due to delegation), check the following:
+/// - the NS name should have an address record (AAAA or A). Failure of
+/// this check is considered a non critical issue.
+/// - the NS name does not have a CNAME. This is prohibited by Section
+/// 10.3 of RFC 2181.
+/// - the NS name is not subject to DNAME substitution. This is prohibited
+/// by Section 4 of RFC 6672.
+/// .
+///
+/// In addition, when the check is completed without any critical error, this
+/// function guarantees that RRsets for the SOA and (apex) NS stored in the
+/// passed RRset collection have the expected type of Rdata objects,
+/// i.e., generic::SOA and generic::NS, respectively. (This is normally
+/// expected to be the case, but not guaranteed by the API).
+///
+/// As for the check on the existence of AAAA or A records for NS names,
+/// it should be noted that BIND 9 treats this as a critical error.
+/// It's not clear whether it's an implementation dependent behavior or
+/// based on the protocol standard (it looks like the former), but to make
+/// it sure we need to confirm there is even no wildcard match for the names.
+/// This should be a very rare configuration, and more expensive to detect,
+/// so we do not check this condition, and treat this case as a non critical
+/// issue.
+///
+/// This function indicates the result of the checks (whether there is a
+/// critical error) via the return value: It returns \c true if there is no
+/// critical error and returns \c false otherwise. It doesn't throw an
+/// exception on encountering an error so that it can report as many errors
+/// as possible in a single call. If an exception is a better way to signal
+/// the error, the caller can pass a callback object that throws from its
+/// \c error() method.
+///
+/// This function can still throw an exception if it finds a really bogus
+/// condition that is most likely to be an implementation bug of the caller.
+/// Such cases include when an RRset contained in the RRset collection is
+/// empty.
+///
+/// \throw Unexpected Conditions that suggest a caller's bug (see the
+/// description)
+///
+/// \param zone_name The name of the zone to be checked
+/// \param zone_class The RR class of the zone to be checked
+/// \param zone_rrsets The collection of RRsets of the zone
+/// \param callbacks Callback object used to report errors and issues
+///
+/// \return \c true if no critical errors are found; \c false otherwise.
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks);
+
+} // namespace dns
+} // namespace isc
+#endif // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/log/log_messages.mes b/src/lib/log/log_messages.mes
index f150f39..4bc8c98 100644
--- a/src/lib/log/log_messages.mes
+++ b/src/lib/log/log_messages.mes
@@ -97,7 +97,7 @@ There may be several reasons why this message may appear:
- The message ID has been mis-spelled in the local message file.
- The program outputting the message may not use that particular message
-(e.g. it originates in a module not used by the program.)
+(e.g. it originates in a module not used by the program).
- The local file was written for an earlier version of the BIND 10 software
and the later version no longer generates that message.
@@ -131,7 +131,7 @@ version of BIND 10.
% LOG_READING_LOCAL_FILE reading local message file %1
This is an informational message output by BIND 10 when it starts to read
a local message file. (A local message file may replace the text of
-one of more messages; the ID of the message will not be changed though.)
+one or more messages; the ID of the message will not be changed though.)
% LOG_READ_ERROR error reading from message file %1: %2
The specified error was encountered reading from the named message file.
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 6862d0c..711eae9 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.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 <algorithm>
#include <iostream>
@@ -21,6 +23,7 @@
#include <log4cplus/consoleappender.h>
#include <log4cplus/fileappender.h>
#include <log4cplus/syslogappender.h>
+#include <log4cplus/helpers/loglog.h>
#include <log/logger.h>
#include <log/logger_support.h>
@@ -196,6 +199,13 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
{
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+ // Disable log4cplus' own logging, unless --enable-debug was
+ // specified to configure. Note that this does not change
+ // LogLog's levels (that is still just INFO).
+#ifndef ENABLE_DEBUG
+ log4cplus::helpers::LogLog::getLogLog()->setQuietMode(true);
+#endif
+
// Set the log4cplus root to not output anything - effectively we are
// ignoring it.
log4cplus::Logger::getRoot().setLogLevel(log4cplus::OFF_LOG_LEVEL);
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 1f7006c..febeb10 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -177,8 +177,14 @@ class BaseComponent:
self._start_internal()
except Exception as e:
logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
- self.failed(None)
- raise
+ try:
+ self.failed(None)
+ finally:
+ # Even failed() can fail if this happens during initial startup
+ # time. In that case we'd rather propagate the original reason
+ # for the failure than the fact that failed() failed. So we
+ # always re-raise the original exception.
+ raise e
def stop(self):
"""
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 688ccf5..dcd9b64 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -17,11 +17,7 @@ from isc.bind10.component import Component, BaseComponent
import isc.bind10.sockcreator
from bind10_config import LIBEXECPATH
import os
-import posix
import isc.log
-from isc.log_messages.bind10_messages import *
-
-logger = isc.log.Logger("boss")
class SockCreator(BaseComponent):
"""
@@ -36,8 +32,6 @@ class SockCreator(BaseComponent):
def __init__(self, process, boss, kind, address=None, params=None):
BaseComponent.__init__(self, boss, kind)
self.__creator = None
- self.__uid = boss.uid
- self.__gid = boss.gid
def _start_internal(self):
self._boss.curproc = 'b10-sockcreator'
@@ -46,12 +40,9 @@ class SockCreator(BaseComponent):
self._boss.register_process(self.pid(), self)
self._boss.set_creator(self.__creator)
self._boss.log_started(self.pid())
- if self.__gid is not None:
- logger.info(BIND10_SETGID, self.__gid)
- posix.setgid(self.__gid)
- if self.__uid is not None:
- logger.info(BIND10_SETUID, self.__uid)
- posix.setuid(self.__uid)
+
+ # We are now ready for switching user.
+ self._boss.change_user()
def _stop_internal(self):
self.__creator.terminate()
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 18efea7..8603201 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,10 +104,10 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.__stop_process_params = None
self.__start_simple_params = None
# Pretending to be boss
- self.gid = None
- self.__gid_set = None
- self.uid = None
- self.__uid_set = None
+ self.__change_user_called = False
+
+ def change_user(self):
+ self.__change_user_called = True # just record the fact it's called
def __start(self):
"""
@@ -624,12 +624,6 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.assertTrue(process.killed)
self.assertFalse(process.terminated)
- def setgid(self, gid):
- self.__gid_set = gid
-
- def setuid(self, uid):
- self.__uid_set = uid
-
class FakeCreator:
def pid(self):
return 42
@@ -655,35 +649,19 @@ class ComponentTests(BossUtils, unittest.TestCase):
"""
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- orig_setgid = isc.bind10.special_component.posix.setgid
- orig_setuid = isc.bind10.special_component.posix.setuid
- isc.bind10.special_component.posix.setgid = self.setgid
- isc.bind10.special_component.posix.setuid = self.setuid
orig_creator = \
isc.bind10.special_component.isc.bind10.sockcreator.Creator
# Just ignore the creator call
isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
lambda path: self.FakeCreator()
component.start()
- # No gid/uid set in boss, nothing called.
- self.assertIsNone(self.__gid_set)
- self.assertIsNone(self.__uid_set)
+ self.assertTrue(self.__change_user_called)
# Doesn't do anything, but doesn't crash
component.stop()
component.kill()
component.kill(True)
- self.gid = 4200
- self.uid = 42
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- component.start()
- # This time, it get's called
- self.assertEqual(4200, self.__gid_set)
- self.assertEqual(42, self.__uid_set)
- isc.bind10.special_component.posix.setgid = orig_setgid
- isc.bind10.special_component.posix.setuid = orig_setuid
- isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
- orig_creator
class TestComponent(BaseComponent):
"""
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 83bf0ed..9e66016 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -100,15 +100,12 @@ Note: This is a tentative API, and this method is likely to change or\n\
be removed in the near future. For that reason, it currently provides\n\
a default implementation that throws NotImplemented.\n\
\n\
-Apart from the two exceptions mentioned below, in theory this call can\n\
-throw anything, depending on the implementation of the datasource\n\
-backend.\n\
-\n\
Exceptions:\n\
NotImplemented If the datasource backend does not support direct\n\
zone creation.\n\
DataSourceError If something goes wrong in the data source while\n\
- creating the zone.\n\
+ creating the zone, or there are some really unexpected\n\
+ errors.\n\
\n\
Parameters:\n\
name The (fully qualified) name of the zone to create\n\
@@ -117,6 +114,42 @@ Return Value(s): True if the zone was added, False if it already\n\
existed\n\
";
+const char* const DataSourceClient_deleteZone_doc = "\
+delete_zone(zone_name) -> bool\n\
+\n\
+Delete a zone from the data source.\n\
+\n\
+This method also checks if the specified zone exists in the data\n\
+source, and returns true/false depending on whether the zone\n\
+existed/not existed, respectively. In either case, on successful\n\
+return it ensures the data source does not contain the specified name\n\
+of the zone.\n\
+\n\
+Note: This is a tentative API, and this method is likely to change or\n\
+be removed in the near future. For that reason, it currently provides\n\
+a default implementation that throws NotImplemented. Note also that\n\
+this method does not delete other database records related to the\n\
+zone, such as zone's resource records or differences corresponding to\n\
+updates made in the zone. This is primarily for implementation\n\
+simplicity (in the currently intended usage there wouldn't be such\n\
+other data at the time of this call anyway) and due to the fact that\n\
+details of managing zones is still in flux. Once the design in this\n\
+area is fixed we may revisit the behavior.\n\
+\n\
+Exceptions:\n\
+ NotImplemented If the datasource backend does not support direct\n\
+ zone deletion.\n\
+ DataSourceError If something goes wrong in the data source while\n\
+ deleting the zone, or there are some really unexpected\n\
+ errors.\n\
+\n\
+Parameters:\n\
+ zone_name The (fully qualified) name of the zone to be deleted\n\
+\n\
+Return Value(s): true if the zone previously existed and has been\n\
+deleted by this method; false if the zone didn't exist.\n\
+";
+
const char* const DataSourceClient_getIterator_doc = "\
get_iterator(name, separate_rrs=False) -> ZoneIterator\n\
\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index f360445..9e4bd42 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -123,6 +123,36 @@ DataSourceClient_createZone(PyObject* po_self, PyObject* args) {
}
PyObject*
+DataSourceClient_deleteZone(PyObject* po_self, PyObject* args) {
+ s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+ PyObject* po_name;
+ if (PyArg_ParseTuple(args, "O!", &name_type, &po_name)) {
+ try {
+ if (self->client->deleteZone(PyName_ToName(po_name))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const isc::NotImplemented& ni) {
+ PyErr_SetString(getDataSourceException("NotImplemented"),
+ ni.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+ }
+ return (NULL);
+}
+
+PyObject*
DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
PyObject* name_obj;
@@ -263,6 +293,8 @@ PyMethodDef DataSourceClient_methods[] = {
DataSourceClient_findZone_doc },
{ "create_zone", DataSourceClient_createZone, METH_VARARGS,
DataSourceClient_createZone_doc },
+ { "delete_zone", DataSourceClient_deleteZone, METH_VARARGS,
+ DataSourceClient_deleteZone_doc },
{ "get_iterator",
DataSourceClient_getIterator, METH_VARARGS,
DataSourceClient_getIterator_doc },
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
index 58ea8cd..0d94a86 100644
--- a/src/lib/python/isc/datasrc/tests/.gitignore
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -1 +1,2 @@
/*.sqlite3.copied
+/zoneloadertest.sqlite3
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index fef04a4..659e7a8 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -634,23 +634,27 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(None, iterator.get_soa())
self.assertEqual(None, iterator.get_next_rrset())
- def test_create_zone_args(self):
+ def test_create_or_delete_zone_args(self):
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
- self.assertRaises(TypeError, dsc.create_zone)
- self.assertRaises(TypeError, dsc.create_zone, 1)
- self.assertRaises(TypeError, dsc.create_zone, None)
- self.assertRaises(TypeError, dsc.create_zone, "foo.")
- self.assertRaises(TypeError, dsc.create_zone,
- isc.dns.Name("example.org"), 1)
+ for method in [dsc.create_zone, dsc.delete_zone]:
+ self.assertRaises(TypeError, method)
+ self.assertRaises(TypeError, method, 1)
+ self.assertRaises(TypeError, method, None)
+ self.assertRaises(TypeError, method, "foo.")
+ self.assertRaises(TypeError, method, isc.dns.Name("example.org"),
+ 1)
- def test_create_zone(self):
+ def test_create_delete_zone(self):
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
# Note, using example.org here, which should not exist
zone_name = isc.dns.Name("example.org")
self.assertIsNone(dsc.get_updater(zone_name, True))
self.assertRaises(isc.datasrc.Error, dsc.get_iterator, zone_name)
+ # delete_zone should return False, meaning it didn't exist.
+ self.assertFalse(dsc.delete_zone(zone_name))
+
self.assertTrue(dsc.create_zone(zone_name))
# should exist now, we should be able to get an updater
@@ -663,6 +667,12 @@ class DataSrcUpdater(unittest.TestCase):
# Trying to create it again should return False
self.assertFalse(dsc.create_zone(zone_name))
+ # Now that it exists, delete_zone should return True, and it cannot
+ # be found any more.
+ self.assertTrue(dsc.delete_zone(zone_name))
+ self.assertEqual(isc.datasrc.DataSourceClient.NOTFOUND,
+ dsc.find_zone(zone_name)[0])
+
def test_create_zone_locked(self):
zone_name = isc.dns.Name("example.org")
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
@@ -670,10 +680,13 @@ class DataSrcUpdater(unittest.TestCase):
# Should fail since db is locked
self.assertRaises(isc.datasrc.Error, dsc.create_zone, zone_name)
+ self.assertRaises(isc.datasrc.Error, dsc.delete_zone,
+ isc.dns.Name('example.com'))
- # Cancel updater, then create should succeed
+ # Cancel updater, then create/delete should succeed
updater = None
self.assertTrue(dsc.create_zone(zone_name))
+ self.assertTrue(dsc.delete_zone(zone_name))
def test_create_zone_not_implemented(self):
mem_cfg = '{ "type": "memory", "class": "IN", "zones": [] }';
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 7e34e70..406151c 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -122,7 +122,7 @@ as the class in the Zone Section, ANY, or NONE.
A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
-An error occured while committing the DDNS update changes to the
+An error occurred while committing the DDNS update changes to the
datasource. The specific error is printed. A SERVFAIL response is sent
back to the client.
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index 7b95201..108efe3 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -40,7 +40,7 @@ using boost::bind;
// (tags/RELEASE_28 115909)) on OSX, where unwinding the stack
// segfaults the moment this exception was thrown and caught.
//
-// Placing it in a named namespace instead of the originalRecommend
+// Placing it in a named namespace instead of the original
// unnamed namespace appears to solve this, so as a temporary
// workaround, we create a local randomly named namespace here
// to solve this issue.
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 2e86ba3..97ff6e6 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -17,6 +17,7 @@ EXTRA_DIST += libxfrin_messages.py
EXTRA_DIST += loadzone_messages.py
EXTRA_DIST += server_common_messages.py
EXTRA_DIST += dbutil_messages.py
+EXTRA_DIST += msgq_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -35,6 +36,7 @@ CLEANFILES += libxfrin_messages.pyc
CLEANFILES += loadzone_messages.pyc
CLEANFILES += server_common_messages.pyc
CLEANFILES += dbutil_messages.pyc
+CLEANFILES += msgq_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/msgq_messages.py b/src/lib/python/isc/log_messages/msgq_messages.py
new file mode 100644
index 0000000..81efa41
--- /dev/null
+++ b/src/lib/python/isc/log_messages/msgq_messages.py
@@ -0,0 +1 @@
+from work.msgq_messages import *
diff --git a/src/lib/resolve/resolve_messages.mes b/src/lib/resolve/resolve_messages.mes
index 1df544a..6447082 100644
--- a/src/lib/resolve/resolve_messages.mes
+++ b/src/lib/resolve/resolve_messages.mes
@@ -86,7 +86,7 @@ SERVFAIL will be returned.
% RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response
A debug message, the response to the specified query from an upstream
-nameserver was a CNAME that had mutiple RRs in the RRset. This is
+nameserver was a CNAME that had multiple RRs in the RRset. This is
an invalid response according to the standards so a SERVFAIL will be
returned to the system making the original query.
@@ -138,7 +138,7 @@ A debug message, the response to the specified query indicated an error
that is not covered by a specific code path. A SERVFAIL will be returned.
% RESLIB_RECQ_CACHE_FIND found <%1> in the cache (resolve() instance %2)
-This is a debug message and indicates that a RecursiveQuery object found the
+This is a debug message and indicates that a RecursiveQuery object found
the specified <name, class, type> tuple in the cache. The instance number
at the end of the message indicates which of the two resolve() methods has
been called.
@@ -178,7 +178,7 @@ A debug message giving the round-trip time of the last query and response.
This is a debug message and indicates that a RunningQuery object found
the specified <name, class, type> tuple in the cache.
-% RESLIB_RUNQ_CACHE_LOOKUP looking up up <%1> in the cache
+% RESLIB_RUNQ_CACHE_LOOKUP looking up <%1> in the cache
This is a debug message and indicates that a RunningQuery object has made
a call to its doLookup() method to look up the specified <name, class, type>
tuple, the first action of which will be to examine the cache.
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 0e4efa5..22ce0f3 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -17,11 +17,11 @@ $NAMESPACE isc::server_common
# \brief Messages for the server_common library
% SOCKETREQUESTOR_CREATED Socket requestor created for application %1
-Debug message. A socket requesor (client of the socket creator) is created
+Debug message. A socket requestor (client of the socket creator) is created
for the corresponding application. Normally this should happen at most
one time throughout the lifetime of the application.
-% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+% SOCKETREQUESTOR_DESTROYED Socket requestor destroyed
Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
has been destroyed. This event is generally unexpected other than in
test cases.
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 262d177..6de01ec 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -208,10 +208,9 @@ pullSigs(std::vector<isc::dns::ConstRRsetPtr>& rrsets,
{
for (ITERATOR it = begin; it != end; ++it) {
rrsets.push_back(*it);
- text += (*it)->toText();
+ text += (*it)->toText(); // this will include RRSIG, if attached.
if ((*it)->getRRsig()) {
rrsets.push_back((*it)->getRRsig());
- text += (*it)->getRRsig()->toText();
}
}
}
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
index 0026a0b..c38f901 100644
--- a/src/lib/util/encode/base_n.cc
+++ b/src/lib/util/encode/base_n.cc
@@ -291,7 +291,8 @@ BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
isc_throw(BadValue, "Too many " << algorithm
<< " padding characters: " << input);
}
- } else if (ch < 0 || !isspace(ch)) {
+ } else if (!(ch > 0 && isspace(ch))) {
+ // see the note for DecodeNormalizer::skipSpaces() above for ch > 0
break;
}
++srit;
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
index 31b1016..7a9a323 100755
--- a/tests/system/bindctl/setup.sh
+++ b/tests/system/bindctl/setup.sh
@@ -22,5 +22,5 @@ SUBTEST_TOP=${TEST_TOP}/bindctl
cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
rm -f ${SUBTEST_TOP}/*/zone.sqlite3
-${B10_LOADZONE} -c '{"database_file": "'${SUBTEST_TOP}/nsx1/zone.sqlite3'"}' \
+${B10_LOADZONE} -i 1 -c "{\"database_file\": \"${SUBTEST_TOP}/nsx1/zone.sqlite3\"}" \
. ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
index 9611c90..75c91de 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -85,6 +85,7 @@ config commit
quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
# perform a simple check on the output (digcomp would be too much for this)
grep 192.0.2.1 dig.out.$n > /dev/null || status=1
@@ -118,6 +119,7 @@ config commit
quit
" | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
@@ -148,6 +150,7 @@ quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
done
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
@@ -183,6 +186,7 @@ quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
done
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
index 92f72fa..4948d63 100755
--- a/tests/system/conf.sh.in
+++ b/tests/system/conf.sh.in
@@ -51,7 +51,10 @@ export RNDC=$BIND9_TOP/bin/rndc/rndc
export TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
export DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
-export SUBDIRS="bindctl glue ixfr/in-2"
+# bindctl test doesn't work right now and is disabled (see #2568)
+#export SUBDIRS="bindctl glue ixfr/in-2"
+export SUBDIRS="glue ixfr/in-2"
+
# Add appropriate subdirectories to the above statement as the tests become
# available.
#SUBDIRS="dnssec masterfile ixfr/in-1 ixfr/in-2 ixfr/in-4"
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
index dc5b28a..2f9f886 100755
--- a/tests/system/glue/setup.sh.in
+++ b/tests/system/glue/setup.sh.in
@@ -18,8 +18,8 @@ SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
rm -f */zone.sqlite3
-${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db
-${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' . @builddir@/nsx1/root.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' root-servers.nil \
@builddir@/nsx1/root-servers.nil.db
-${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db
-${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' com @builddir@/nsx1/com.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' net @builddir@/nsx1/net.db
diff --git a/tests/system/ixfr/in-1/setup.sh.in b/tests/system/ixfr/in-1/setup.sh.in
index d4c3978..4332930 100644
--- a/tests/system/ixfr/in-1/setup.sh.in
+++ b/tests/system/ixfr/in-1/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n4 ns1/db.example
# Set up the IXFR client - load the same version of the zone.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n4
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n4
diff --git a/tests/system/ixfr/in-2/setup.sh.in b/tests/system/ixfr/in-2/setup.sh.in
index a5f64e5..a210636 100644
--- a/tests/system/ixfr/in-2/setup.sh.in
+++ b/tests/system/ixfr/in-2/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n6 ns1/db.example
# Set up the IXFR client - load an earlier version of the zone
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n6
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n6
diff --git a/tests/system/ixfr/in-3/setup.sh.in b/tests/system/ixfr/in-3/setup.sh.in
index 867e06e..2a08c58 100644
--- a/tests/system/ixfr/in-3/setup.sh.in
+++ b/tests/system/ixfr/in-3/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n0 ns1/db.example
# Set up the IXFR client - load a previous version of the zone.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2
diff --git a/tests/system/ixfr/in-4/setup.sh.in b/tests/system/ixfr/in-4/setup.sh.in
index 7419e27..1c2e9c8 100644
--- a/tests/system/ixfr/in-4/setup.sh.in
+++ b/tests/system/ixfr/in-4/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n2.refresh ns1/db.example
# Set up the IXFR client - load a previous version of the zone with a short
# refresh time.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2.refresh
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2.refresh
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 3ccef94..8b7e974 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -13,7 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/libdhcp++.h>
-#include <dhcp/dhcp6.h>
+#include <dhcp/dhcp4.h>
#include "perf_pkt4.h"
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 9376972..f7f4978 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -1284,6 +1284,10 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
if (!pkt4) {
isc_throw(Unexpected, "failed to create DISCOVER packet");
}
+
+ // Delete the default Message Type option set by Pkt4
+ pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
+
// Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
OptionBuffer buf_msg_type;
buf_msg_type.push_back(DHCPDISCOVER);
@@ -1371,11 +1375,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
const dhcp::Pkt4Ptr& offer_pkt4) {
const uint32_t transid = generateTransid();
Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
- OptionBuffer buf_msg_type;
- buf_msg_type.push_back(DHCPREQUEST);
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- buf_msg_type);
- pkt4->addOption(opt_msg_type);
+
// Use first flags indicates that we want to use the server
// id captured in first packet.
if (CommandOptions::instance().isUseFirst() &&
@@ -1414,9 +1414,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
setDefaults4(socket, pkt4);
// Set hardware address
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
- pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
+ pkt4->setHWAddr(offer_pkt4->getHWAddr());
// Set elapsed time.
uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
@@ -1461,8 +1459,10 @@ TestControl::sendRequest4(const TestControlSocket& socket,
transid));
// Set hardware address from OFFER packet received.
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
+ HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
+ std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
+ uint8_t hw_len = hwaddr->hwaddr_.size();
+ memcpy(&mac_address[0], &hwaddr->hwaddr_[0], hw_len > 16 ? 16 : hw_len);
pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
// Set elapsed time.
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
index d6d1ec8..5697f95 100644
--- a/tests/tools/perfdhcp/tests/.gitignore
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -1 +1,6 @@
/run_unittests
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index 6c27731..ae67e6e 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -630,13 +630,10 @@ private:
boost::shared_ptr<Pkt4>
createOfferPkt4(uint32_t transid) const {
boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- OptionBuffer(DHCPOFFER));
OptionPtr opt_serverid = Option::factory(Option::V4,
DHO_DHCP_SERVER_IDENTIFIER,
OptionBuffer(4, 1));
offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
- offer->addOption(opt_msg_type);
offer->addOption(opt_serverid);
offer->updateTimestamp();
return (offer);
More information about the bind10-changes
mailing list