mysql_host_data_source.cc 115 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897
  1. // Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <dhcp/libdhcp++.h>
  8. #include <dhcp/option.h>
  9. #include <dhcp/option_definition.h>
  10. #include <dhcp/option_space.h>
  11. #include <dhcpsrv/cfg_option.h>
  12. #include <dhcpsrv/db_exceptions.h>
  13. #include <dhcpsrv/dhcpsrv_log.h>
  14. #include <dhcpsrv/mysql_host_data_source.h>
  15. #include <dhcpsrv/db_exceptions.h>
  16. #include <util/buffer.h>
  17. #include <util/optional_value.h>
  18. #include <boost/algorithm/string/split.hpp>
  19. #include <boost/algorithm/string/classification.hpp>
  20. #include <boost/array.hpp>
  21. #include <boost/pointer_cast.hpp>
  22. #include <boost/static_assert.hpp>
  23. #include <mysql.h>
  24. #include <mysqld_error.h>
  25. #include <stdint.h>
  26. #include <string>
  27. using namespace isc;
  28. using namespace isc::asiolink;
  29. using namespace isc::dhcp;
  30. using namespace isc::util;
  31. using namespace std;
  32. namespace {
  33. /// @brief Maximum size of an IPv6 address represented as a text string.
  34. ///
  35. /// This is 32 hexadecimal characters written in 8 groups of four, plus seven
  36. /// colon separators.
  37. const size_t ADDRESS6_TEXT_MAX_LEN = 39;
  38. /// @brief Maximum length of classes stored in a dhcp4/6_client_classes
  39. /// columns.
  40. const size_t CLIENT_CLASSES_MAX_LEN = 255;
  41. /// @brief Maximum length of the hostname stored in DNS.
  42. ///
  43. /// This length is restricted by the length of the domain-name carried
  44. /// in the Client FQDN %Option (see RFC4702 and RFC4704).
  45. const size_t HOSTNAME_MAX_LEN = 255;
  46. /// @brief Maximum length of option value.
  47. const size_t OPTION_VALUE_MAX_LEN = 4096;
  48. /// @brief Maximum length of option value specified in textual format.
  49. const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
  50. /// @brief Maximum length of option space name.
  51. const size_t OPTION_SPACE_MAX_LEN = 128;
  52. /// @brief Maximum length of the server hostname.
  53. const size_t SERVER_HOSTNAME_MAX_LEN = 64;
  54. /// @brief Maximum length of the boot file name.
  55. const size_t BOOT_FILE_NAME_MAX_LEN = 128;
  56. /// @brief Numeric value representing last supported identifier.
  57. ///
  58. /// This value is used to validate whether the identifier type stored in
  59. /// a database is within bounds. of supported identifiers.
  60. const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
  61. /// @brief This class provides mechanisms for sending and retrieving
  62. /// information from the 'hosts' table.
  63. ///
  64. /// This class is used to insert and retrieve entries from the 'hosts' table.
  65. /// The queries used with this class do not retrieve IPv6 reservations or
  66. /// options associated with a host to minimize impact on performance. Other
  67. /// classes derived from @ref MySqlHostExchange should be used to retrieve
  68. /// information about IPv6 reservations and options.
  69. class MySqlHostExchange {
  70. private:
  71. /// @brief Number of columns returned for SELECT queries send by this class.
  72. static const size_t HOST_COLUMNS = 12;
  73. public:
  74. /// @brief Constructor
  75. ///
  76. /// @param additional_columns_num This value is set by the derived classes
  77. /// to indicate how many additional columns will be returned by SELECT
  78. /// queries performed by the derived class. This constructor will allocate
  79. /// resources for these columns, e.g. binding table, error indicators.
  80. MySqlHostExchange(const size_t additional_columns_num = 0)
  81. : columns_num_(HOST_COLUMNS + additional_columns_num),
  82. bind_(columns_num_), columns_(columns_num_),
  83. error_(columns_num_, MLM_FALSE), host_id_(0),
  84. dhcp_identifier_length_(0), dhcp_identifier_type_(0),
  85. dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
  86. hostname_length_(0), dhcp4_client_classes_length_(0),
  87. dhcp6_client_classes_length_(0),
  88. dhcp4_next_server_(0),
  89. dhcp4_server_hostname_length_(0),
  90. dhcp4_boot_file_name_length_(0),
  91. dhcp4_subnet_id_null_(MLM_FALSE),
  92. dhcp6_subnet_id_null_(MLM_FALSE),
  93. ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
  94. dhcp4_client_classes_null_(MLM_FALSE),
  95. dhcp6_client_classes_null_(MLM_FALSE),
  96. dhcp4_next_server_null_(MLM_FALSE),
  97. dhcp4_server_hostname_null_(MLM_FALSE),
  98. dhcp4_boot_file_name_null_(MLM_FALSE) {
  99. // Fill arrays with 0 so as they don't include any garbage.
  100. memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
  101. memset(hostname_, 0, sizeof(hostname_));
  102. memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
  103. memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
  104. memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
  105. memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
  106. // Set the column names for use by this class. This only comprises
  107. // names used by the MySqlHostExchange class. Derived classes will
  108. // need to set names for the columns they use.
  109. columns_[0] = "host_id";
  110. columns_[1] = "dhcp_identifier";
  111. columns_[2] = "dhcp_identifier_type";
  112. columns_[3] = "dhcp4_subnet_id";
  113. columns_[4] = "dhcp6_subnet_id";
  114. columns_[5] = "ipv4_address";
  115. columns_[6] = "hostname";
  116. columns_[7] = "dhcp4_client_classes";
  117. columns_[8] = "dhcp6_client_classes";
  118. columns_[9] = "dhcp4_next_server";
  119. columns_[10] = "dhcp4_server_hostname";
  120. columns_[11] = "dhcp4_boot_file_name";
  121. BOOST_STATIC_ASSERT(11 < HOST_COLUMNS);
  122. };
  123. /// @brief Virtual destructor.
  124. virtual ~MySqlHostExchange() {
  125. }
  126. /// @brief Returns index of the first uninitialized column name.
  127. ///
  128. /// This method is called by the derived classes to determine which
  129. /// column indexes are available for the derived classes within a
  130. /// binding array, error array and column names. This method
  131. /// determines the first available index by searching the first
  132. /// empty value within the columns_ vector. Previously we relied on
  133. /// the fixed values set for each class, but this was hard to maintain
  134. /// when new columns were added to the SELECT queries. It required
  135. /// modifying indexes in all derived classes.
  136. ///
  137. /// Derived classes must call this method in their constructors and
  138. /// use returned value as an index for the first column used by the
  139. /// derived class and increment this value for each subsequent column.
  140. size_t findAvailColumn() const {
  141. std::vector<std::string>::const_iterator empty_column =
  142. std::find(columns_.begin(), columns_.end(), std::string());
  143. return (std::distance(columns_.begin(), empty_column));
  144. }
  145. /// @brief Returns value of host id.
  146. ///
  147. /// This method is used by derived classes.
  148. uint64_t getHostId() const {
  149. return (host_id_);
  150. };
  151. /// @brief Set error indicators
  152. ///
  153. /// Sets the error indicator for each of the MYSQL_BIND elements. It points
  154. /// the "error" field within an element of the input array to the
  155. /// corresponding element of the passed error array.
  156. ///
  157. /// @param bind Array of BIND elements
  158. /// @param error Array of error elements. If there is an error in getting
  159. /// data associated with one of the "bind" elements, the
  160. /// corresponding element in the error array is set to MLM_TRUE.
  161. static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
  162. std::vector<my_bool>& error) {
  163. for (size_t i = 0; i < error.size(); ++i) {
  164. error[i] = MLM_FALSE;
  165. bind[i].error = reinterpret_cast<char*>(&error[i]);
  166. }
  167. };
  168. /// @brief Return columns in error
  169. ///
  170. /// If an error is returned from a fetch (in particular, a truncated
  171. /// status), this method can be called to get the names of the fields in
  172. /// error. It returns a string comprising the names of the fields
  173. /// separated by commas. In the case of there being no error indicators
  174. /// set, it returns the string "(None)".
  175. ///
  176. /// @param error Array of error elements. An element is set to MLM_TRUE
  177. /// if the corresponding column in the database is the source of
  178. /// the error.
  179. /// @param names Array of column names, the same size as the error array.
  180. /// @param count Size of each of the arrays.
  181. static std::string getColumnsInError(std::vector<my_bool>& error,
  182. const std::vector<std::string>& names) {
  183. std::string result = "";
  184. // Accumulate list of column names
  185. for (size_t i = 0; i < names.size(); ++i) {
  186. if (error[i] == MLM_TRUE) {
  187. if (!result.empty()) {
  188. result += ", ";
  189. }
  190. result += names[i];
  191. }
  192. }
  193. if (result.empty()) {
  194. result = "(None)";
  195. }
  196. return (result);
  197. };
  198. /// @brief Create MYSQL_BIND objects for Host Pointer
  199. ///
  200. /// Fills in the MYSQL_BIND array for sending data stored in the Host object
  201. /// to the database.
  202. ///
  203. /// @param host Host object to be added to the database.
  204. /// None of the fields in the host reservation are modified -
  205. /// the host data is only read.
  206. ///
  207. /// @return Vector of MySQL BIND objects representing the data to be added.
  208. std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
  209. // Store host object to ensure it remains valid.
  210. host_ = host;
  211. // Initialize prior to constructing the array of MYSQL_BIND structures.
  212. // It sets all fields, including is_null, to zero, so we need to set
  213. // is_null only if it should be true. This gives up minor performance
  214. // benefit while being safe approach.
  215. memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
  216. // Set up the structures for the various components of the host structure.
  217. try {
  218. // host_id : INT UNSIGNED NOT NULL
  219. // The host_id is auto_incremented by MySQL database,
  220. // so we need to pass the NULL value
  221. host_id_ = 0;
  222. bind_[0].buffer_type = MYSQL_TYPE_LONG;
  223. bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
  224. bind_[0].is_unsigned = MLM_TRUE;
  225. // dhcp_identifier : VARBINARY(128) NOT NULL
  226. dhcp_identifier_length_ = host->getIdentifier().size();
  227. memcpy(static_cast<void*>(dhcp_identifier_buffer_),
  228. &(host->getIdentifier())[0],
  229. host->getIdentifier().size());
  230. bind_[1].buffer_type = MYSQL_TYPE_BLOB;
  231. bind_[1].buffer = dhcp_identifier_buffer_;
  232. bind_[1].buffer_length = dhcp_identifier_length_;
  233. bind_[1].length = &dhcp_identifier_length_;
  234. // dhcp_identifier_type : TINYINT NOT NULL
  235. dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
  236. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  237. bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
  238. bind_[2].is_unsigned = MLM_TRUE;
  239. // dhcp4_subnet_id : INT UNSIGNED NULL
  240. // Can't take an address of intermediate object, so let's store it
  241. // in dhcp4_subnet_id_
  242. dhcp4_subnet_id_ = host->getIPv4SubnetID();
  243. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  244. bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
  245. bind_[3].is_unsigned = MLM_TRUE;
  246. // dhcp6_subnet_id : INT UNSIGNED NULL
  247. // Can't take an address of intermediate object, so let's store it
  248. // in dhcp6_subnet_id_
  249. dhcp6_subnet_id_ = host->getIPv6SubnetID();
  250. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  251. bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
  252. bind_[4].is_unsigned = MLM_TRUE;
  253. // ipv4_address : INT UNSIGNED NULL
  254. // The address in the Host structure is an IOAddress object. Convert
  255. // this to an integer for storage.
  256. ipv4_address_ = host->getIPv4Reservation().toUint32();
  257. ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
  258. bind_[5].buffer_type = MYSQL_TYPE_LONG;
  259. bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
  260. bind_[5].is_unsigned = MLM_TRUE;
  261. bind_[5].is_null = &ipv4_address_null_;
  262. // hostname : VARCHAR(255) NULL
  263. strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
  264. hostname_length_ = host->getHostname().length();
  265. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  266. bind_[6].buffer = reinterpret_cast<char*>(hostname_);
  267. bind_[6].buffer_length = hostname_length_;
  268. // dhcp4_client_classes : VARCHAR(255) NULL
  269. bind_[7].buffer_type = MYSQL_TYPE_STRING;
  270. // Override default separator to not include space after comma.
  271. string classes4_txt = host->getClientClasses4().toText(",");
  272. strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
  273. bind_[7].buffer = dhcp4_client_classes_;
  274. bind_[7].buffer_length = classes4_txt.length();
  275. // dhcp6_client_classes : VARCHAR(255) NULL
  276. bind_[8].buffer_type = MYSQL_TYPE_STRING;
  277. // Override default separator to not include space after comma.
  278. string classes6_txt = host->getClientClasses6().toText(",");
  279. strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
  280. bind_[8].buffer = dhcp6_client_classes_;
  281. bind_[8].buffer_length = classes6_txt.length();
  282. // ipv4_address : INT UNSIGNED NULL
  283. // The address in the Host structure is an IOAddress object. Convert
  284. // this to an integer for storage.
  285. dhcp4_next_server_ = host->getNextServer().toUint32();
  286. bind_[9].buffer_type = MYSQL_TYPE_LONG;
  287. bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
  288. bind_[9].is_unsigned = MLM_TRUE;
  289. // bind_[9].is_null = &MLM_FALSE; // commented out for performance
  290. // reasons, see memset() above
  291. // dhcp4_server_hostname
  292. bind_[10].buffer_type = MYSQL_TYPE_STRING;
  293. std::string server_hostname = host->getServerHostname();
  294. strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
  295. SERVER_HOSTNAME_MAX_LEN - 1);
  296. bind_[10].buffer = dhcp4_server_hostname_;
  297. bind_[10].buffer_length = server_hostname.length();
  298. // dhcp4_boot_file_name
  299. bind_[11].buffer_type = MYSQL_TYPE_STRING;
  300. std::string boot_file_name = host->getBootFileName();
  301. strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
  302. BOOT_FILE_NAME_MAX_LEN - 1);
  303. bind_[11].buffer = dhcp4_boot_file_name_;
  304. bind_[11].buffer_length = boot_file_name.length();
  305. } catch (const std::exception& ex) {
  306. isc_throw(DbOperationError,
  307. "Could not create bind array from Host: "
  308. << host->getHostname() << ", reason: " << ex.what());
  309. }
  310. // Add the data to the vector. Note the end element is one after the
  311. // end of the array.
  312. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[columns_num_]));
  313. };
  314. /// @brief Create BIND array to receive Host data.
  315. ///
  316. /// Creates a MYSQL_BIND array to receive Host data from the database.
  317. /// After data is successfully received, @ref retrieveHost can be called
  318. /// to retrieve the Host object.
  319. ///
  320. /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
  321. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  322. // Initialize MYSQL_BIND array.
  323. // It sets all fields, including is_null, to zero, so we need to set
  324. // is_null only if it should be true. This gives up minor performance
  325. // benefit while being safe approach. For improved readability, the
  326. // code that explicitly sets is_null is there, but is commented out.
  327. // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
  328. memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
  329. // host_id : INT UNSIGNED NOT NULL
  330. bind_[0].buffer_type = MYSQL_TYPE_LONG;
  331. bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
  332. bind_[0].is_unsigned = MLM_TRUE;
  333. // dhcp_identifier : VARBINARY(128) NOT NULL
  334. dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
  335. bind_[1].buffer_type = MYSQL_TYPE_BLOB;
  336. bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
  337. bind_[1].buffer_length = dhcp_identifier_length_;
  338. bind_[1].length = &dhcp_identifier_length_;
  339. // dhcp_identifier_type : TINYINT NOT NULL
  340. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  341. bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
  342. bind_[2].is_unsigned = MLM_TRUE;
  343. // dhcp4_subnet_id : INT UNSIGNED NULL
  344. dhcp4_subnet_id_null_ = MLM_FALSE;
  345. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  346. bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
  347. bind_[3].is_unsigned = MLM_TRUE;
  348. bind_[3].is_null = &dhcp4_subnet_id_null_;
  349. // dhcp6_subnet_id : INT UNSIGNED NULL
  350. dhcp6_subnet_id_null_ = MLM_FALSE;
  351. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  352. bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
  353. bind_[4].is_unsigned = MLM_TRUE;
  354. bind_[4].is_null = &dhcp6_subnet_id_null_;
  355. // ipv4_address : INT UNSIGNED NULL
  356. ipv4_address_null_ = MLM_FALSE;
  357. bind_[5].buffer_type = MYSQL_TYPE_LONG;
  358. bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
  359. bind_[5].is_unsigned = MLM_TRUE;
  360. bind_[5].is_null = &ipv4_address_null_;
  361. // hostname : VARCHAR(255) NULL
  362. hostname_null_ = MLM_FALSE;
  363. hostname_length_ = sizeof(hostname_);
  364. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  365. bind_[6].buffer = reinterpret_cast<char*>(hostname_);
  366. bind_[6].buffer_length = hostname_length_;
  367. bind_[6].length = &hostname_length_;
  368. bind_[6].is_null = &hostname_null_;
  369. // dhcp4_client_classes : VARCHAR(255) NULL
  370. dhcp4_client_classes_null_ = MLM_FALSE;
  371. dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
  372. bind_[7].buffer_type = MYSQL_TYPE_STRING;
  373. bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
  374. bind_[7].buffer_length = dhcp4_client_classes_length_;
  375. bind_[7].length = &dhcp4_client_classes_length_;
  376. bind_[7].is_null = &dhcp4_client_classes_null_;
  377. // dhcp6_client_classes : VARCHAR(255) NULL
  378. dhcp6_client_classes_null_ = MLM_FALSE;
  379. dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
  380. bind_[8].buffer_type = MYSQL_TYPE_STRING;
  381. bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
  382. bind_[8].buffer_length = dhcp6_client_classes_length_;
  383. bind_[8].length = &dhcp6_client_classes_length_;
  384. bind_[8].is_null = &dhcp6_client_classes_null_;
  385. // dhcp4_next_server
  386. dhcp4_next_server_null_ = MLM_FALSE;
  387. bind_[9].buffer_type = MYSQL_TYPE_LONG;
  388. bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
  389. bind_[9].is_unsigned = MLM_TRUE;
  390. bind_[9].is_null = &dhcp4_next_server_null_;
  391. // dhcp4_server_hostname
  392. dhcp4_server_hostname_null_ = MLM_FALSE;
  393. dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
  394. bind_[10].buffer_type = MYSQL_TYPE_STRING;
  395. bind_[10].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
  396. bind_[10].buffer_length = dhcp4_server_hostname_length_;
  397. bind_[10].length = &dhcp4_server_hostname_length_;
  398. bind_[10].is_null = &dhcp4_server_hostname_null_;
  399. // dhcp4_boot_file_name
  400. dhcp4_boot_file_name_null_ = MLM_FALSE;
  401. dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
  402. bind_[11].buffer_type = MYSQL_TYPE_STRING;
  403. bind_[11].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
  404. bind_[11].buffer_length = dhcp4_boot_file_name_length_;
  405. bind_[11].length = &dhcp4_boot_file_name_length_;
  406. bind_[11].is_null = &dhcp4_boot_file_name_null_;
  407. // Add the error flags
  408. setErrorIndicators(bind_, error_);
  409. // Add the data to the vector. Note the end element is one after the
  410. // end of the array.
  411. return (bind_);
  412. };
  413. /// @brief Copy received data into Host object
  414. ///
  415. /// This function copies information about the host into a newly created
  416. /// @ref Host object. This method is called after @ref createBindForReceive.
  417. /// has been used.
  418. ///
  419. /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
  420. /// @ref Host object returned.
  421. HostPtr retrieveHost() {
  422. // Check if the identifier stored in the database is correct.
  423. if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
  424. isc_throw(BadValue, "invalid dhcp identifier type returned: "
  425. << static_cast<int>(dhcp_identifier_type_));
  426. }
  427. // Set the dhcp identifier type in a variable of the appropriate
  428. // data type.
  429. Host::IdentifierType type =
  430. static_cast<Host::IdentifierType>(dhcp_identifier_type_);
  431. // Set DHCPv4 subnet ID to the value returned. If NULL returned,
  432. // set to 0.
  433. SubnetID ipv4_subnet_id(0);
  434. if (dhcp4_subnet_id_null_ == MLM_FALSE) {
  435. ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
  436. }
  437. // Set DHCPv6 subnet ID to the value returned. If NULL returned,
  438. // set to 0.
  439. SubnetID ipv6_subnet_id(0);
  440. if (dhcp6_subnet_id_null_ == MLM_FALSE) {
  441. ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
  442. }
  443. // Set IPv4 address reservation if it was given, if not, set IPv4 zero
  444. // address
  445. asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
  446. if (ipv4_address_null_ == MLM_FALSE) {
  447. ipv4_reservation = asiolink::IOAddress(ipv4_address_);
  448. }
  449. // Set hostname if non NULL value returned. Otherwise, leave an
  450. // empty string.
  451. std::string hostname;
  452. if (hostname_null_ == MLM_FALSE) {
  453. hostname = std::string(hostname_, hostname_length_);
  454. }
  455. // Set DHCPv4 client classes if non NULL value returned.
  456. std::string dhcp4_client_classes;
  457. if (dhcp4_client_classes_null_ == MLM_FALSE) {
  458. dhcp4_client_classes = std::string(dhcp4_client_classes_,
  459. dhcp4_client_classes_length_);
  460. }
  461. // Set DHCPv6 client classes if non NULL value returned.
  462. std::string dhcp6_client_classes;
  463. if (dhcp6_client_classes_null_ == MLM_FALSE) {
  464. dhcp6_client_classes = std::string(dhcp6_client_classes_,
  465. dhcp6_client_classes_length_);
  466. }
  467. // Set next server value (siaddr) if non NULL value returned.
  468. asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
  469. if (dhcp4_next_server_null_ == MLM_FALSE) {
  470. next_server = asiolink::IOAddress(dhcp4_next_server_);
  471. }
  472. // Set server hostname (sname) if non NULL value returned.
  473. std::string dhcp4_server_hostname;
  474. if (dhcp4_server_hostname_null_ == MLM_FALSE) {
  475. dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
  476. dhcp4_server_hostname_length_);
  477. }
  478. // Set boot file name (file) if non NULL value returned.
  479. std::string dhcp4_boot_file_name;
  480. if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
  481. dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
  482. dhcp4_boot_file_name_length_);
  483. }
  484. // Create and return Host object from the data gathered.
  485. HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
  486. type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
  487. hostname, dhcp4_client_classes, dhcp6_client_classes,
  488. next_server, dhcp4_server_hostname,
  489. dhcp4_boot_file_name));
  490. h->setHostId(host_id_);
  491. return (h);
  492. };
  493. /// @brief Processes one row of data fetched from a database.
  494. ///
  495. /// The processed data must contain host id, which uniquely identifies a
  496. /// host. This method creates a host and inserts it to the hosts collection
  497. /// only if the last inserted host has a different host id. This prevents
  498. /// adding duplicated hosts to the collection, assuming that processed
  499. /// rows are primarily ordered by host id column.
  500. ///
  501. /// This method must be overriden in the derived classes to also
  502. /// retrieve IPv6 reservations and DHCP options associated with a host.
  503. ///
  504. /// @param [out] hosts Collection of hosts to which a new host created
  505. /// from the processed data should be inserted.
  506. virtual void processFetchedData(ConstHostCollection& hosts) {
  507. HostPtr host;
  508. // Add new host only if there are no hosts yet or the host id of the
  509. // most recently added host is different than the host id of the
  510. // currently processed host.
  511. if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
  512. // Create Host object from the fetched data and append it to the
  513. // collection.
  514. host = retrieveHost();
  515. hosts.push_back(host);
  516. }
  517. }
  518. /// @brief Return columns in error
  519. ///
  520. /// If an error is returned from a fetch (in particular, a truncated
  521. /// status), this method can be called to get the names of the fields in
  522. /// error. It returns a string comprising the names of the fields
  523. /// separated by commas. In the case of there being no error indicators
  524. /// set, it returns the string "(None)".
  525. ///
  526. /// @return Comma-separated list of columns in error, or the string
  527. /// "(None)".
  528. std::string getErrorColumns() {
  529. return (getColumnsInError(error_, columns_));
  530. };
  531. protected:
  532. /// Number of columns returned in queries.
  533. size_t columns_num_;
  534. /// Vector of MySQL bindings.
  535. std::vector<MYSQL_BIND> bind_;
  536. /// Column names.
  537. std::vector<std::string> columns_;
  538. /// Error array.
  539. std::vector<my_bool> error_;
  540. /// Pointer to Host object holding information to be inserted into
  541. /// Hosts table.
  542. HostPtr host_;
  543. private:
  544. /// Host identifier (primary key in Hosts table).
  545. uint64_t host_id_;
  546. /// Buffer holding client's identifier (e.g. DUID, HW address)
  547. /// in the binary format.
  548. uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN];
  549. /// Length of a data in the dhcp_identifier_buffer_.
  550. unsigned long dhcp_identifier_length_;
  551. /// Type of the identifier in the dhcp_identifier_buffer_. This
  552. /// value corresponds to the @ref Host::IdentifierType value.
  553. uint8_t dhcp_identifier_type_;
  554. /// DHCPv4 subnet identifier.
  555. uint32_t dhcp4_subnet_id_;
  556. /// DHCPv6 subnet identifier.
  557. uint32_t dhcp6_subnet_id_;
  558. /// Reserved IPv4 address.
  559. uint32_t ipv4_address_;
  560. /// Name reserved for the host.
  561. char hostname_[HOSTNAME_MAX_LEN];
  562. /// Hostname length.
  563. unsigned long hostname_length_;
  564. /// A string holding comma separated list of DHCPv4 client classes.
  565. char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
  566. /// A length of the string holding comma separated list of DHCPv4
  567. /// client classes.
  568. unsigned long dhcp4_client_classes_length_;
  569. /// A string holding comma separated list of DHCPv6 client classes.
  570. char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
  571. /// A length of the string holding comma separated list of DHCPv6
  572. /// client classes.
  573. unsigned long dhcp6_client_classes_length_;
  574. /// Next server address (siaddr).
  575. uint32_t dhcp4_next_server_;
  576. /// Server hostname (sname).
  577. char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];
  578. /// A length of the string holding server hostname.
  579. unsigned long dhcp4_server_hostname_length_;
  580. /// Boot file name (file).
  581. char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];
  582. /// A length of the string holding boot file name.
  583. unsigned long dhcp4_boot_file_name_length_;
  584. /// @name Boolean values indicating if values of specific columns in
  585. /// the database are NULL.
  586. //@{
  587. /// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
  588. my_bool dhcp4_subnet_id_null_;
  589. /// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
  590. my_bool dhcp6_subnet_id_null_;
  591. /// Boolean flag indicating if the value of IPv4 reservation is NULL.
  592. my_bool ipv4_address_null_;
  593. /// Boolean flag indicating if the value if hostname is NULL.
  594. my_bool hostname_null_;
  595. /// Boolean flag indicating if the value of DHCPv4 client classes is
  596. /// NULL.
  597. my_bool dhcp4_client_classes_null_;
  598. /// Boolean flag indicating if the value of DHCPv6 client classes is
  599. /// NULL.
  600. my_bool dhcp6_client_classes_null_;
  601. /// Boolean flag indicating if the value of next server is NULL.
  602. my_bool dhcp4_next_server_null_;
  603. /// Boolean flag indicating if the value of server hostname is NULL.
  604. my_bool dhcp4_server_hostname_null_;
  605. /// Boolean flag indicating if the value of boot file name is NULL.
  606. my_bool dhcp4_boot_file_name_null_;
  607. //@}
  608. };
  609. /// @brief Extends base exchange class with ability to retrieve DHCP options
  610. /// from the 'dhcp4_options' and 'dhcp6_options' tables.
  611. ///
  612. /// This class provides means to retrieve both DHCPv4 and DHCPv6 options
  613. /// along with the host information. It is not used to retrieve IPv6
  614. /// reservations. The following types of queries are supported:
  615. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
  616. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
  617. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
  618. class MySqlHostWithOptionsExchange : public MySqlHostExchange {
  619. private:
  620. /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
  621. static const size_t OPTION_COLUMNS = 6;
  622. /// @brief Receives DHCPv4 or DHCPv6 options information from the
  623. /// dhcp4_options or dhcp6_options tables respectively.
  624. ///
  625. /// The MySqlHostWithOptionsExchange class holds two respective instances
  626. /// of this class, one for receiving DHCPv4 options, one for receiving
  627. /// DHCPv6 options.
  628. ///
  629. /// The following are the basic functions of this class:
  630. /// - bind class members to specific columns in MySQL binding tables,
  631. /// - set DHCP options specific column names,
  632. /// - create instances of options retrieved from the database.
  633. ///
  634. /// The reason for isolating those functions in a separate C++ class is
  635. /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
  636. class OptionProcessor {
  637. public:
  638. /// @brief Constructor.
  639. ///
  640. /// @param universe V4 or V6. The type of the options' instances
  641. /// created by this class depends on this parameter.
  642. /// @param start_column Index of the first column to be used by this
  643. /// class.
  644. OptionProcessor(const Option::Universe& universe,
  645. const size_t start_column)
  646. : universe_(universe), start_column_(start_column), option_id_(0),
  647. code_(0), value_length_(0), formatted_value_length_(0),
  648. space_length_(0), persistent_(false), option_id_null_(MLM_FALSE),
  649. code_null_(MLM_FALSE), value_null_(MLM_FALSE),
  650. formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE),
  651. option_id_index_(start_column), code_index_(start_column_ + 1),
  652. value_index_(start_column_ + 2),
  653. formatted_value_index_(start_column_ + 3),
  654. space_index_(start_column_ + 4),
  655. persistent_index_(start_column_ + 5),
  656. most_recent_option_id_(0) {
  657. memset(value_, 0, sizeof(value_));
  658. memset(formatted_value_, 0, sizeof(formatted_value_));
  659. memset(space_, 0, sizeof(space_));
  660. }
  661. /// @brief Returns identifier of the currently processed option.
  662. uint64_t getOptionId() const {
  663. if (option_id_null_ == MLM_FALSE) {
  664. return (option_id_);
  665. }
  666. return (0);
  667. }
  668. /// @brief Creates instance of the currently processed option.
  669. ///
  670. /// This method detects if the currently processed option is a new
  671. /// instance. It makes it determination by comparing the identifier
  672. /// of the currently processed option, with the most recently processed
  673. /// option. If the current value is greater than the id of the recently
  674. /// processed option it is assumed that the processed row holds new
  675. /// option information. In such case the option instance is created and
  676. /// inserted into the configuration passed as argument.
  677. ///
  678. /// @param cfg Pointer to the configuration object into which new
  679. /// option instances should be inserted.
  680. void retrieveOption(const CfgOptionPtr& cfg) {
  681. // option_id may be NULL if dhcp4_options or dhcp6_options table
  682. // doesn't contain any options for the particular host. Also, the
  683. // current option id must be greater than id if the most recent
  684. // option because options are ordered by option id. Otherwise
  685. // we assume that this is already processed option.
  686. if ((option_id_null_ == MLM_TRUE) ||
  687. (most_recent_option_id_ >= option_id_)) {
  688. return;
  689. }
  690. // Remember current option id as the most recent processed one. We
  691. // will be comparing it with option ids in subsequent rows.
  692. most_recent_option_id_ = option_id_;
  693. // Convert it to string object for easier comparison.
  694. std::string space;
  695. if (space_null_ == MLM_FALSE) {
  696. // Typically, the string values returned by the database are not
  697. // NULL terminated.
  698. space_[space_length_] = '\0';
  699. space.assign(space_);
  700. }
  701. // If empty or null space provided, use a default top level space.
  702. if (space.empty()) {
  703. space = (universe_ == Option::V4 ? "dhcp4" : "dhcp6");
  704. }
  705. // Convert formatted_value to string as well.
  706. std::string formatted_value;
  707. if (formatted_value_null_ == MLM_FALSE) {
  708. formatted_value_[formatted_value_length_] = '\0';
  709. formatted_value.assign(formatted_value_);
  710. }
  711. // Options are held in a binary or textual format in the database.
  712. // This is similar to having an option specified in a server
  713. // configuration file. Such option is converted to appropriate C++
  714. // class, using option definition. Thus, we need to find the
  715. // option definition for this option code and option space.
  716. // Check if this is a standard option.
  717. OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
  718. // Otherwise, we may check if this an option encapsulated within the
  719. // vendor space.
  720. if (!def && (space != DHCP4_OPTION_SPACE) &&
  721. (space != DHCP6_OPTION_SPACE)) {
  722. uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
  723. if (vendor_id > 0) {
  724. def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
  725. }
  726. }
  727. // In all other cases, we use runtime option definitions, which
  728. // should be also registered within the libdhcp++.
  729. if (!def) {
  730. def = LibDHCP::getRuntimeOptionDef(space, code_);
  731. }
  732. OptionPtr option;
  733. if (!def) {
  734. // If no definition found, we use generic option type.
  735. OptionBuffer buf(value_, value_ + value_length_);
  736. option.reset(new Option(universe_, code_, buf.begin(),
  737. buf.end()));
  738. } else {
  739. // The option value may be specified in textual or binary format
  740. // in the database. If formatted_value is empty, the binary
  741. // format is used. Depending on the format we use a different
  742. // variant of the optionFactory function.
  743. if (formatted_value.empty()) {
  744. OptionBuffer buf(value_, value_ + value_length_);
  745. option = def->optionFactory(universe_, code_, buf.begin(),
  746. buf.end());
  747. } else {
  748. // Spit the value specified in comma separated values
  749. // format.
  750. std::vector<std::string> split_vec;
  751. boost::split(split_vec, formatted_value, boost::is_any_of(","));
  752. option = def->optionFactory(universe_, code_, split_vec);
  753. }
  754. }
  755. OptionDescriptor desc(option, persistent_, formatted_value);
  756. cfg->add(desc, space);
  757. }
  758. /// @brief Specify column names.
  759. ///
  760. /// @param [out] columns Reference to a vector holding names of option
  761. /// specific columns.
  762. void setColumnNames(std::vector<std::string>& columns) {
  763. columns[option_id_index_] = "option_id";
  764. columns[code_index_] = "code";
  765. columns[value_index_] = "value";
  766. columns[formatted_value_index_] = "formatted_value";
  767. columns[space_index_] = "space";
  768. columns[persistent_index_] = "persistent";
  769. }
  770. /// @brief Initialize binding table fields for options.
  771. ///
  772. /// Resets most_recent_option_id_ value to 0.
  773. ///
  774. /// @param [out] bind Binding table.
  775. void setBindFields(std::vector<MYSQL_BIND>& bind) {
  776. // This method is called just before making a new query, so we
  777. // reset the most_recent_option_id_ to start over with options
  778. // processing.
  779. most_recent_option_id_ = 0;
  780. // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
  781. bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
  782. bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
  783. bind[option_id_index_].is_unsigned = MLM_TRUE;
  784. // code : TINYINT OR SHORT UNSIGNED NOT NULL
  785. bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
  786. bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
  787. bind[code_index_].is_unsigned = MLM_TRUE;
  788. bind[code_index_].is_null = &code_null_;
  789. // value : BLOB NULL
  790. value_length_ = sizeof(value_);
  791. bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
  792. bind[value_index_].buffer = reinterpret_cast<char*>(value_);
  793. bind[value_index_].buffer_length = value_length_;
  794. bind[value_index_].length = &value_length_;
  795. bind[value_index_].is_null = &value_null_;
  796. // formatted_value : TEXT NULL
  797. formatted_value_length_ = sizeof(formatted_value_);
  798. bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
  799. bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
  800. bind[formatted_value_index_].buffer_length = formatted_value_length_;
  801. bind[formatted_value_index_].length = &formatted_value_length_;
  802. bind[formatted_value_index_].is_null = &formatted_value_null_;
  803. // space : VARCHAR(128) NULL
  804. space_length_ = sizeof(space_);
  805. bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
  806. bind[space_index_].buffer = reinterpret_cast<char*>(space_);
  807. bind[space_index_].buffer_length = space_length_;
  808. bind[space_index_].length = &space_length_;
  809. bind[space_index_].is_null = &space_null_;
  810. // persistent : TINYINT(1) NOT NULL DEFAULT 0
  811. bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
  812. bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
  813. bind[persistent_index_].is_unsigned = MLM_TRUE;
  814. }
  815. private:
  816. /// @brief Universe: V4 or V6.
  817. Option::Universe universe_;
  818. /// @brief Index of first column used by this class.
  819. size_t start_column_;
  820. /// @brief Option id.
  821. uint32_t option_id_;
  822. /// @brief Option code.
  823. uint16_t code_;
  824. /// @brief Buffer holding binary value of an option.
  825. uint8_t value_[OPTION_VALUE_MAX_LEN];
  826. /// @brief Option value length.
  827. unsigned long value_length_;
  828. /// @brief Buffer holding textual value of an option.
  829. char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
  830. /// @brief Formatted option value length.
  831. unsigned long formatted_value_length_;
  832. /// @brief Buffer holding option space name.
  833. char space_[OPTION_SPACE_MAX_LEN];
  834. /// @brief Option space length.
  835. unsigned long space_length_;
  836. /// @brief Flag indicating if option is always sent or only if
  837. /// requested.
  838. bool persistent_;
  839. /// @name Boolean values indicating if values of specific columns in
  840. /// the database are NULL.
  841. //@{
  842. /// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
  843. my_bool option_id_null_;
  844. /// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
  845. my_bool code_null_;
  846. /// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
  847. my_bool value_null_;
  848. /// @brief Boolean flag indicating if the DHCPv4 formatted option value
  849. /// is NULL.
  850. my_bool formatted_value_null_;
  851. /// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
  852. my_bool space_null_;
  853. //@}
  854. /// @name Indexes of the specific columns
  855. //@{
  856. /// @brief Option id
  857. size_t option_id_index_;
  858. /// @brief Code
  859. size_t code_index_;
  860. /// @brief Value
  861. size_t value_index_;
  862. /// @brief Formatted value
  863. size_t formatted_value_index_;
  864. /// @brief Space
  865. size_t space_index_;
  866. /// @brief Persistent
  867. size_t persistent_index_;
  868. //@}
  869. /// @brief Option id for last processed row.
  870. uint32_t most_recent_option_id_;
  871. };
  872. /// @brief Pointer to the @ref OptionProcessor class.
  873. typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
  874. public:
  875. /// @brief DHCP option types to be fetched from the database.
  876. ///
  877. /// Supported types are:
  878. /// - Only DHCPv4 options,
  879. /// - Only DHCPv6 options,
  880. /// - Both DHCPv4 and DHCPv6 options.
  881. enum FetchedOptions {
  882. DHCP4_ONLY,
  883. DHCP6_ONLY,
  884. DHCP4_AND_DHCP6
  885. };
  886. /// @brief Constructor.
  887. ///
  888. /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
  889. /// be fetched from the database for a host.
  890. /// @param additional_columns_num Number of additional columns for which
  891. /// resources should be allocated, e.g. binding table, column names etc.
  892. /// This parameter should be set to a non zero value by derived classes to
  893. /// allocate resources for the columns supported by derived classes.
  894. MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
  895. const size_t additional_columns_num = 0)
  896. : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
  897. + additional_columns_num),
  898. opt_proc4_(), opt_proc6_() {
  899. // Create option processor for DHCPv4 options, if required.
  900. if ((fetched_options == DHCP4_ONLY) ||
  901. (fetched_options == DHCP4_AND_DHCP6)) {
  902. opt_proc4_.reset(new OptionProcessor(Option::V4,
  903. findAvailColumn()));
  904. opt_proc4_->setColumnNames(columns_);
  905. }
  906. // Create option processor for DHCPv6 options, if required.
  907. if ((fetched_options == DHCP6_ONLY) ||
  908. (fetched_options == DHCP4_AND_DHCP6)) {
  909. opt_proc6_.reset(new OptionProcessor(Option::V6,
  910. findAvailColumn()));
  911. opt_proc6_->setColumnNames(columns_);
  912. }
  913. }
  914. /// @brief Processes the current row.
  915. ///
  916. /// The processed row includes both host information and DHCP option
  917. /// information. Because used SELECT query use LEFT JOIN clause, the
  918. /// some rows contain duplicated host or options entries. This method
  919. /// detects duplicated information and discards such entries.
  920. ///
  921. /// @param [out] hosts Container holding parsed hosts and options.
  922. virtual void processFetchedData(ConstHostCollection& hosts) {
  923. // Holds pointer to the previously parsed host.
  924. HostPtr most_recent_host;
  925. if (!hosts.empty()) {
  926. // Const cast is not very elegant way to deal with it, but
  927. // there is a good reason to use it here. This method is called
  928. // to build a collection of const hosts to be returned to the
  929. // caller. If we wanted to use non-const collection we'd need
  930. // to copy the whole collection before returning it, which has
  931. // performance implications. Alternatively, we could store the
  932. // most recently added host in a class member but this would
  933. // make the code less readable.
  934. most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
  935. }
  936. // If no host has been parsed yet or we're at the row holding next
  937. // host, we create a new host object and put it at the end of the
  938. // list.
  939. if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
  940. HostPtr host = retrieveHost();
  941. hosts.push_back(host);
  942. most_recent_host = host;
  943. }
  944. // Parse DHCPv4 options if required to do so.
  945. if (opt_proc4_) {
  946. CfgOptionPtr cfg = most_recent_host->getCfgOption4();
  947. opt_proc4_->retrieveOption(cfg);
  948. }
  949. // Parse DHCPv6 options if required to do so.
  950. if (opt_proc6_) {
  951. CfgOptionPtr cfg = most_recent_host->getCfgOption6();
  952. opt_proc6_->retrieveOption(cfg);
  953. }
  954. }
  955. /// @brief Bind variables for receiving option data.
  956. ///
  957. /// @return Vector of MYSQL_BIND object representing data to be retrieved.
  958. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  959. // The following call sets bind_ values between 0 and 8.
  960. static_cast<void>(MySqlHostExchange::createBindForReceive());
  961. // Bind variables for DHCPv4 options.
  962. if (opt_proc4_) {
  963. opt_proc4_->setBindFields(bind_);
  964. }
  965. // Bind variables for DHCPv6 options.
  966. if (opt_proc6_) {
  967. opt_proc6_->setBindFields(bind_);
  968. }
  969. // Add the error flags
  970. setErrorIndicators(bind_, error_);
  971. return (bind_);
  972. };
  973. private:
  974. /// @brief Returns a number of columns required to retrieve option data.
  975. ///
  976. /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
  977. /// DHCPv6 a different number of columns is required in the binding array.
  978. /// This method returns the number of required columns, according to the
  979. /// value of @c fetched_columns passed in the constructor.
  980. ///
  981. /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
  982. /// both types of options should be retrieved.
  983. ///
  984. /// @return Number of required columns.
  985. static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
  986. return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
  987. OPTION_COLUMNS);
  988. }
  989. /// @brief Pointer to DHCPv4 options processor.
  990. ///
  991. /// If this object is NULL, the DHCPv4 options are not fetched.
  992. OptionProcessorPtr opt_proc4_;
  993. /// @brief Pointer to DHCPv6 options processor.
  994. ///
  995. /// If this object is NULL, the DHCPv6 options are not fetched.
  996. OptionProcessorPtr opt_proc6_;
  997. };
  998. /// @brief This class provides mechanisms for sending and retrieving
  999. /// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
  1000. ///
  1001. /// This class extends the @ref MySqlHostWithOptionsExchange class with the
  1002. /// mechanisms to retrieve IPv6 reservations. This class is used in situations
  1003. /// when it is desired to retrieve DHCPv6 specific information about the host
  1004. /// (DHCPv6 options and reservations), or entire information about the host
  1005. /// (DHCPv4 options, DHCPv6 options and reservations). The following are the
  1006. /// queries used with this class:
  1007. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
  1008. /// LEFT JOIN ipv6_reservations ...
  1009. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
  1010. class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
  1011. private:
  1012. /// @brief Number of columns holding IPv6 reservation information.
  1013. static const size_t RESERVATION_COLUMNS = 5;
  1014. public:
  1015. /// @brief Constructor.
  1016. ///
  1017. /// Apart from initializing the base class data structures it also
  1018. /// initializes values representing IPv6 reservation information.
  1019. MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
  1020. : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
  1021. reservation_id_(0),
  1022. reserv_type_(0), reserv_type_null_(MLM_FALSE),
  1023. ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
  1024. reservation_id_index_(findAvailColumn()),
  1025. address_index_(reservation_id_index_ + 1),
  1026. prefix_len_index_(reservation_id_index_ + 2),
  1027. type_index_(reservation_id_index_ + 3),
  1028. iaid_index_(reservation_id_index_ + 4),
  1029. most_recent_reservation_id_(0) {
  1030. memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
  1031. // Provide names of additional columns returned by the queries.
  1032. columns_[reservation_id_index_] = "reservation_id";
  1033. columns_[address_index_] = "address";
  1034. columns_[prefix_len_index_] = "prefix_len";
  1035. columns_[type_index_] = "type";
  1036. columns_[iaid_index_] = "dhcp6_iaid";
  1037. }
  1038. /// @brief Returns last fetched reservation id.
  1039. ///
  1040. /// @return Reservation id or 0 if no reservation data is fetched.
  1041. uint32_t getReservationId() const {
  1042. if (reserv_type_null_ == MLM_FALSE) {
  1043. return (reservation_id_);
  1044. }
  1045. return (0);
  1046. };
  1047. /// @brief Creates IPv6 reservation from the data contained in the
  1048. /// currently processed row.
  1049. ///
  1050. /// Called after the MYSQL_BIND array created by createBindForReceive().
  1051. ///
  1052. /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
  1053. IPv6Resrv retrieveReservation() {
  1054. // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
  1055. IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
  1056. switch (reserv_type_) {
  1057. case 0:
  1058. type = IPv6Resrv::TYPE_NA;
  1059. break;
  1060. case 2:
  1061. type = IPv6Resrv::TYPE_PD;
  1062. break;
  1063. default:
  1064. isc_throw(BadValue,
  1065. "invalid IPv6 reservation type returned: "
  1066. << static_cast<int>(reserv_type_)
  1067. << ". Only 0 or 2 are allowed.");
  1068. }
  1069. ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
  1070. std::string address = ipv6_address_buffer_;
  1071. IPv6Resrv r(type, IOAddress(address), prefix_len_);
  1072. return (r);
  1073. };
  1074. /// @brief Processes one row of data fetched from a database.
  1075. ///
  1076. /// The processed data must contain host id, which uniquely identifies a
  1077. /// host. This method creates a host and inserts it to the hosts collection
  1078. /// only if the last inserted host has a different host id. This prevents
  1079. /// adding duplicated hosts to the collection, assuming that processed
  1080. /// rows are primarily ordered by host id column.
  1081. ///
  1082. /// Depending on the value of the @c fetched_options specified in the
  1083. /// constructor, this method also parses options returned as a result
  1084. /// of SELECT queries.
  1085. ///
  1086. /// For any returned row which contains IPv6 reservation information it
  1087. /// checks if the reservation is not a duplicate of previously parsed
  1088. /// reservation and appends the IPv6Resrv object into the host object
  1089. /// if the parsed row contains new reservation information.
  1090. ///
  1091. /// @param [out] hosts Collection of hosts to which a new host created
  1092. /// from the processed data should be inserted.
  1093. virtual void processFetchedData(ConstHostCollection& hosts) {
  1094. // Call parent class to fetch host information and options.
  1095. MySqlHostWithOptionsExchange::processFetchedData(hosts);
  1096. if (getReservationId() == 0) {
  1097. return;
  1098. }
  1099. if (hosts.empty()) {
  1100. isc_throw(Unexpected, "no host information while retrieving"
  1101. " IPv6 reservation");
  1102. }
  1103. HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
  1104. // If we're dealing with a new reservation, let's add it to the
  1105. // host.
  1106. if (getReservationId() > most_recent_reservation_id_) {
  1107. most_recent_reservation_id_ = getReservationId();
  1108. if (most_recent_reservation_id_ > 0) {
  1109. host->addReservation(retrieveReservation());
  1110. }
  1111. }
  1112. }
  1113. /// @brief Create BIND array to receive Host data with IPv6 reservations.
  1114. ///
  1115. /// Creates a MYSQL_BIND array to receive Host data from the database.
  1116. /// After data is successfully received, @ref processedFetchedData is
  1117. /// called for each returned row to build collection of @ref Host
  1118. /// objects with associated IPv6 reservations.
  1119. ///
  1120. /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
  1121. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  1122. // Reset most recent reservation id value because we're now making
  1123. // a new SELECT query.
  1124. most_recent_reservation_id_ = 0;
  1125. // Bind values supported by parent classes.
  1126. static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
  1127. // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
  1128. bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
  1129. bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
  1130. bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
  1131. // IPv6 address/prefix VARCHAR(39)
  1132. ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
  1133. bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
  1134. bind_[address_index_].buffer = ipv6_address_buffer_;
  1135. bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
  1136. bind_[address_index_].length = &ipv6_address_buffer_len_;
  1137. // prefix_len : TINYINT
  1138. bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
  1139. bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
  1140. bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
  1141. // (reservation) type : TINYINT
  1142. reserv_type_null_ = MLM_FALSE;
  1143. bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
  1144. bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
  1145. bind_[type_index_].is_unsigned = MLM_TRUE;
  1146. bind_[type_index_].is_null = &reserv_type_null_;
  1147. // dhcp6_iaid INT UNSIGNED
  1148. bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
  1149. bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
  1150. bind_[iaid_index_].is_unsigned = MLM_TRUE;
  1151. // Add the error flags
  1152. setErrorIndicators(bind_, error_);
  1153. return (bind_);
  1154. };
  1155. private:
  1156. /// @brief IPv6 reservation id.
  1157. uint32_t reservation_id_;
  1158. /// @brief IPv6 reservation type.
  1159. uint8_t reserv_type_;
  1160. /// @brief Boolean flag indicating if reservation type field is null.
  1161. ///
  1162. /// This flag is used by the class to determine if the returned row
  1163. /// contains IPv6 reservation information.
  1164. my_bool reserv_type_null_;
  1165. /// @brief Buffer holding IPv6 address/prefix in textual format.
  1166. char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
  1167. /// @brief Length of the textual address representation.
  1168. unsigned long ipv6_address_buffer_len_;
  1169. /// @brief Length of the prefix (128 for addresses)
  1170. uint8_t prefix_len_;
  1171. /// @brief IAID.
  1172. uint32_t iaid_;
  1173. /// @name Indexes of columns holding information about IPv6 reservations.
  1174. //@{
  1175. /// @brief Index of reservation_id column.
  1176. size_t reservation_id_index_;
  1177. /// @brief Index of address column.
  1178. size_t address_index_;
  1179. /// @brief Index of prefix_len column.
  1180. size_t prefix_len_index_;
  1181. /// @brief Index of type column.
  1182. size_t type_index_;
  1183. /// @brief Index of IAID column.
  1184. size_t iaid_index_;
  1185. //@}
  1186. /// @brief Reservation id for last processed row.
  1187. uint32_t most_recent_reservation_id_;
  1188. };
  1189. /// @brief This class is used for storing IPv6 reservations in a MySQL database.
  1190. ///
  1191. /// This class is only used to insert IPv6 reservations into the
  1192. /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
  1193. /// retrieve IPv6 reservation the @ref MySqlIPv6HostExchange class should be
  1194. /// used instead.
  1195. ///
  1196. /// When a new IPv6 reservation is inserted into the database, an appropriate
  1197. /// host must be defined in the hosts table. An attempt to insert IPv6
  1198. /// reservation for non-existing host will result in failure.
  1199. class MySqlIPv6ReservationExchange {
  1200. private:
  1201. /// @brief Set number of columns for ipv6_reservation table.
  1202. static const size_t RESRV_COLUMNS = 6;
  1203. public:
  1204. /// @brief Constructor
  1205. ///
  1206. /// Initialize class members representing a single IPv6 reservation.
  1207. MySqlIPv6ReservationExchange()
  1208. : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
  1209. iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
  1210. // Reset error table.
  1211. std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
  1212. // Set the column names (for error messages)
  1213. columns_[0] = "host_id";
  1214. columns_[1] = "address";
  1215. columns_[2] = "prefix_len";
  1216. columns_[3] = "type";
  1217. columns_[4] = "dhcp6_iaid";
  1218. BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
  1219. }
  1220. /// @brief Create MYSQL_BIND objects for IPv6 Reservation.
  1221. ///
  1222. /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
  1223. /// object to the database.
  1224. ///
  1225. /// @param resv An object representing IPv6 reservation which will be
  1226. /// sent to the database.
  1227. /// None of the fields in the reservation are modified -
  1228. /// the reservation data is only read.
  1229. /// @param id ID of a host owning this reservation
  1230. ///
  1231. /// @return Vector of MySQL BIND objects representing the data to be added.
  1232. std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
  1233. const HostID& id) {
  1234. // Store the values to ensure they remain valid.
  1235. resv_ = resv;
  1236. host_id_ = id;
  1237. // Initialize prior to constructing the array of MYSQL_BIND structures.
  1238. // It sets all fields, including is_null, to zero, so we need to set
  1239. // is_null only if it should be true. This gives up minor performance
  1240. // benefit while being safe approach. For improved readability, the
  1241. // code that explicitly sets is_null is there, but is commented out.
  1242. memset(bind_, 0, sizeof(bind_));
  1243. // Set up the structures for the various components of the host structure.
  1244. try {
  1245. // address VARCHAR(39)
  1246. address_ = resv.getPrefix().toText();
  1247. address_len_ = address_.length();
  1248. bind_[0].buffer_type = MYSQL_TYPE_BLOB;
  1249. bind_[0].buffer = reinterpret_cast<char*>
  1250. (const_cast<char*>(address_.c_str()));
  1251. bind_[0].buffer_length = address_len_;
  1252. bind_[0].length = &address_len_;
  1253. // prefix_len tinyint
  1254. prefix_len_ = resv.getPrefixLen();
  1255. bind_[1].buffer_type = MYSQL_TYPE_TINY;
  1256. bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
  1257. bind_[1].is_unsigned = MLM_TRUE;
  1258. // type tinyint
  1259. // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
  1260. type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
  1261. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  1262. bind_[2].buffer = reinterpret_cast<char*>(&type_);
  1263. bind_[2].is_unsigned = MLM_TRUE;
  1264. // dhcp6_iaid INT UNSIGNED
  1265. /// @todo: We don't support iaid in the IPv6Resrv yet.
  1266. iaid_ = 0;
  1267. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  1268. bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
  1269. bind_[3].is_unsigned = MLM_TRUE;
  1270. // host_id INT UNSIGNED NOT NULL
  1271. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  1272. bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
  1273. bind_[4].is_unsigned = MLM_TRUE;
  1274. } catch (const std::exception& ex) {
  1275. isc_throw(DbOperationError,
  1276. "Could not create bind array from IPv6 Reservation: "
  1277. << resv_.toText() << ", reason: " << ex.what());
  1278. }
  1279. // Add the data to the vector. Note the end element is one after the
  1280. // end of the array.
  1281. // RESRV_COLUMNS -1 as we do not set reservation_id.
  1282. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
  1283. }
  1284. private:
  1285. /// @brief Host unique identifier.
  1286. uint64_t host_id_;
  1287. /// @brief Address (or prefix).
  1288. std::string address_;
  1289. /// @brief Length of the textual address representation.
  1290. unsigned long address_len_;
  1291. /// @brief Length of the prefix (128 for addresses).
  1292. uint8_t prefix_len_;
  1293. /// @brief Reservation type.
  1294. uint8_t type_;
  1295. /// @brief IAID.
  1296. uint8_t iaid_;
  1297. /// @brief Object holding reservation being sent to the database.
  1298. IPv6Resrv resv_;
  1299. /// @brief Array of MySQL bindings.
  1300. MYSQL_BIND bind_[RESRV_COLUMNS];
  1301. /// @brief Array of strings holding columns names.
  1302. std::string columns_[RESRV_COLUMNS];
  1303. /// @brief Array of boolean values indicating if error occurred
  1304. /// for respective columns.
  1305. my_bool error_[RESRV_COLUMNS];
  1306. };
  1307. /// @brief This class is used for inserting options into a database.
  1308. ///
  1309. /// This class supports inserting both DHCPv4 and DHCPv6 options.
  1310. class MySqlOptionExchange {
  1311. private:
  1312. /// @brief Number of columns in the tables holding options.
  1313. static const size_t OPTION_COLUMNS = 9;
  1314. public:
  1315. /// @brief Constructor.
  1316. MySqlOptionExchange()
  1317. : type_(0), value_len_(0), formatted_value_len_(0), space_(), space_len_(0),
  1318. persistent_(false), client_class_(), client_class_len_(0),
  1319. subnet_id_(0), host_id_(0), option_() {
  1320. BOOST_STATIC_ASSERT(8 < OPTION_COLUMNS);
  1321. }
  1322. /// @brief Creates binding array to insert option data into database.
  1323. ///
  1324. /// @return Vector of MYSQL_BIND object representing an option.
  1325. std::vector<MYSQL_BIND>
  1326. createBindForSend(const OptionDescriptor& opt_desc,
  1327. const std::string& opt_space,
  1328. const OptionalValue<SubnetID>& subnet_id,
  1329. const HostID& host_id) {
  1330. // Hold pointer to the option to make sure it remains valid until
  1331. // we complete a query.
  1332. option_ = opt_desc.option_;
  1333. memset(bind_, 0, sizeof(bind_));
  1334. try {
  1335. // option_id: INT UNSIGNED NOT NULL
  1336. // The option_id is auto_incremented, so we need to pass the NULL
  1337. // value.
  1338. bind_[0].buffer_type = MYSQL_TYPE_NULL;
  1339. // code: SMALLINT UNSIGNED NOT NULL
  1340. type_ = option_->getType();
  1341. bind_[1].buffer_type = MYSQL_TYPE_SHORT;
  1342. bind_[1].buffer = reinterpret_cast<char*>(&type_);
  1343. bind_[1].is_unsigned = MLM_TRUE;
  1344. // value: BLOB NULL
  1345. if (opt_desc.formatted_value_.empty() &&
  1346. (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
  1347. // The formatted_value is empty and the option value is
  1348. // non-empty so we need to prepare on-wire format for the
  1349. // option and store it in the database as a blob.
  1350. OutputBuffer buf(opt_desc.option_->len());
  1351. opt_desc.option_->pack(buf);
  1352. const char* buf_ptr = static_cast<const char*>(buf.getData());
  1353. value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
  1354. buf_ptr + buf.getLength());
  1355. value_len_ = value_.size();
  1356. bind_[2].buffer_type = MYSQL_TYPE_BLOB;
  1357. bind_[2].buffer = &value_[0];
  1358. bind_[2].buffer_length = value_len_;
  1359. bind_[2].length = &value_len_;
  1360. } else {
  1361. // No value or formatted_value specified. In this case, the
  1362. // value blob is NULL.
  1363. value_.clear();
  1364. bind_[2].buffer_type = MYSQL_TYPE_NULL;
  1365. }
  1366. // formatted_value: TEXT NULL,
  1367. if (!opt_desc.formatted_value_.empty()) {
  1368. formatted_value_len_ = opt_desc.formatted_value_.size();
  1369. bind_[3].buffer_type = MYSQL_TYPE_STRING;
  1370. bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
  1371. bind_[3].buffer_length = formatted_value_len_;
  1372. bind_[3].length = &formatted_value_len_;
  1373. } else {
  1374. bind_[3].buffer_type = MYSQL_TYPE_NULL;
  1375. }
  1376. // space: VARCHAR(128) NULL
  1377. space_ = opt_space;
  1378. space_len_ = space_.size();
  1379. bind_[4].buffer_type = MYSQL_TYPE_STRING;
  1380. bind_[4].buffer = const_cast<char*>(space_.c_str());
  1381. bind_[4].buffer_length = space_len_;
  1382. bind_[4].length = &space_len_;
  1383. // persistent: TINYINT(1) NOT NULL DEFAULT 0
  1384. persistent_ = opt_desc.persistent_;
  1385. bind_[5].buffer_type = MYSQL_TYPE_TINY;
  1386. bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
  1387. bind_[5].is_unsigned = MLM_TRUE;
  1388. // dhcp_client_class: VARCHAR(128) NULL
  1389. /// @todo Assign actual value to client class string.
  1390. client_class_len_ = client_class_.size();
  1391. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  1392. bind_[6].buffer = const_cast<char*>(client_class_.c_str());
  1393. bind_[6].buffer_length = client_class_len_;
  1394. bind_[6].length = &client_class_len_;
  1395. // dhcp4_subnet_id: INT UNSIGNED NULL
  1396. if (subnet_id.isSpecified()) {
  1397. subnet_id_ = subnet_id;
  1398. bind_[7].buffer_type = MYSQL_TYPE_LONG;
  1399. bind_[7].buffer = reinterpret_cast<char*>(subnet_id_);
  1400. bind_[7].is_unsigned = MLM_TRUE;
  1401. } else {
  1402. bind_[7].buffer_type = MYSQL_TYPE_NULL;
  1403. }
  1404. // host_id: INT UNSIGNED NOT NULL
  1405. host_id_ = host_id;
  1406. bind_[8].buffer_type = MYSQL_TYPE_LONG;
  1407. bind_[8].buffer = reinterpret_cast<char*>(&host_id_);
  1408. bind_[8].is_unsigned = MLM_TRUE;
  1409. } catch (const std::exception& ex) {
  1410. isc_throw(DbOperationError,
  1411. "Could not create bind array for inserting DHCP "
  1412. "option: " << option_->toText() << ", reason: "
  1413. << ex.what());
  1414. }
  1415. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
  1416. }
  1417. private:
  1418. /// @brief Option type.
  1419. uint16_t type_;
  1420. /// @brief Option value as binary.
  1421. std::vector<uint8_t> value_;
  1422. /// @brief Option value length.
  1423. unsigned long value_len_;
  1424. /// @brief Formatted option value length.
  1425. unsigned long formatted_value_len_;
  1426. /// @brief Option space name.
  1427. std::string space_;
  1428. /// @brief Option space name length.
  1429. unsigned long space_len_;
  1430. /// @brief Boolean flag indicating if the option is always returned to
  1431. /// a client or only when requested.
  1432. bool persistent_;
  1433. /// @brief Client classes for the option.
  1434. std::string client_class_;
  1435. /// @brief Length of the string holding client classes for the option.
  1436. unsigned long client_class_len_;
  1437. /// @brief Subnet identifier.
  1438. uint32_t subnet_id_;
  1439. /// @brief Host identifier.
  1440. uint32_t host_id_;
  1441. /// @brief Pointer to currently parsed option.
  1442. OptionPtr option_;
  1443. /// @brief Array of MYSQL_BIND elements representing inserted data.
  1444. MYSQL_BIND bind_[OPTION_COLUMNS];
  1445. };
  1446. } // end of anonymous namespace
  1447. namespace isc {
  1448. namespace dhcp {
  1449. /// @brief Implementation of the @ref MySqlHostDataSource.
  1450. class MySqlHostDataSourceImpl {
  1451. public:
  1452. /// @brief Statement Tags
  1453. ///
  1454. /// The contents of the enum are indexes into the list of SQL statements.
  1455. /// It is assumed that the order is such that the indices of statements
  1456. /// reading the database are less than those of statements modifying the
  1457. /// database.
  1458. enum StatementIndex {
  1459. GET_HOST_DHCPID, // Gets hosts by host identifier
  1460. GET_HOST_ADDR, // Gets hosts by IPv4 address
  1461. GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
  1462. GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
  1463. GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
  1464. GET_HOST_PREFIX, // Gets host by IPv6 prefix
  1465. GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
  1466. GET_VERSION, // Obtain version number
  1467. INSERT_HOST, // Insert new host to collection
  1468. INSERT_V6_RESRV, // Insert v6 reservation
  1469. INSERT_V4_OPTION, // Insert DHCPv4 option
  1470. INSERT_V6_OPTION, // Insert DHCPv6 option
  1471. DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
  1472. DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
  1473. DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
  1474. NUM_STATEMENTS // Number of statements
  1475. };
  1476. /// @brief Index of first statement performing write to the database.
  1477. ///
  1478. /// This value is used to mark border line between queries and other
  1479. /// statements and statements performing write operation on the database,
  1480. /// such as INSERT, DELETE, UPDATE.
  1481. static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
  1482. /// @brief Constructor.
  1483. ///
  1484. /// This constructor opens database connection and initializes prepared
  1485. /// statements used in the queries.
  1486. MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters);
  1487. /// @brief Destructor.
  1488. ~MySqlHostDataSourceImpl();
  1489. /// @brief Executes statements which inserts a row into one of the tables.
  1490. ///
  1491. /// @param stindex Index of a statement being executed.
  1492. /// @param bind Vector of MYSQL_BIND objects to be used when making the
  1493. /// query.
  1494. ///
  1495. /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
  1496. void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
  1497. std::vector<MYSQL_BIND>& bind);
  1498. /// @brief Executes statements that delete records.
  1499. ///
  1500. /// @param stindex Index of a statement being executed.
  1501. /// @param bind Vector of MYSQL_BIND objects to be used when making the
  1502. /// query.
  1503. /// @return true if any records were deleted, false otherwise
  1504. bool
  1505. delStatement(StatementIndex stindex, MYSQL_BIND* bind);
  1506. /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
  1507. ///
  1508. /// @param resv IPv6 Reservation to be added
  1509. /// @param id ID of a host owning this reservation
  1510. void addResv(const IPv6Resrv& resv, const HostID& id);
  1511. /// @brief Inserts a single DHCP option into the database.
  1512. ///
  1513. /// @param stindex Index of a statement being executed.
  1514. /// @param opt_desc Option descriptor holding information about an option
  1515. /// to be inserted into the database.
  1516. /// @param opt_space Option space name.
  1517. /// @param subnet_id Subnet identifier.
  1518. /// @param host_id Host identifier.
  1519. void addOption(const MySqlHostDataSourceImpl::StatementIndex& stindex,
  1520. const OptionDescriptor& opt_desc,
  1521. const std::string& opt_space,
  1522. const OptionalValue<SubnetID>& subnet_id,
  1523. const HostID& host_id);
  1524. /// @brief Inserts multiple options into the database.
  1525. ///
  1526. /// @param stindex Index of a statement being executed.
  1527. /// @param options_cfg An object holding a collection of options to be
  1528. /// inserted into the database.
  1529. /// @param host_id Host identifier retrieved using @c mysql_insert_id.
  1530. void addOptions(const StatementIndex& stindex, const ConstCfgOptionPtr& options_cfg,
  1531. const uint64_t host_id);
  1532. /// @brief Check Error and Throw Exception
  1533. ///
  1534. /// This method invokes @ref MySqlConnection::checkError.
  1535. ///
  1536. /// @param status Status code: non-zero implies an error
  1537. /// @param index Index of statement that caused the error
  1538. /// @param what High-level description of the error
  1539. ///
  1540. /// @throw isc::dhcp::DbOperationError An operation on the open database has
  1541. /// failed.
  1542. void checkError(const int status, const StatementIndex index,
  1543. const char* what) const;
  1544. /// @brief Creates collection of @ref Host objects with associated
  1545. /// information such as IPv6 reservations and/or DHCP options.
  1546. ///
  1547. /// This method performs a query which returns host information from
  1548. /// the 'hosts' table. The query may also use LEFT JOIN clause to
  1549. /// retrieve information from other tables, e.g. ipv6_reservations,
  1550. /// dhcp4_options and dhcp6_options.
  1551. /// Whether IPv6 reservations and/or options are assigned to the
  1552. /// @ref Host objects depends on the type of the exchange object.
  1553. ///
  1554. /// @param stindex Statement index.
  1555. /// @param bind Pointer to an array of MySQL bindings.
  1556. /// @param exchange Pointer to the exchange object used for the
  1557. /// particular query.
  1558. /// @param [out] result Reference to the collection of hosts returned.
  1559. /// @param single A boolean value indicating if a single host is
  1560. /// expected to be returned, or multiple hosts.
  1561. void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
  1562. boost::shared_ptr<MySqlHostExchange> exchange,
  1563. ConstHostCollection& result, bool single) const;
  1564. /// @brief Retrieves a host by subnet and client's unique identifier.
  1565. ///
  1566. /// This method is used by both MySqlHostDataSource::get4 and
  1567. /// MySqlHOstDataSource::get6 methods.
  1568. ///
  1569. /// @param subnet_id Subnet identifier.
  1570. /// @param identifier_type Identifier type.
  1571. /// @param identifier_begin Pointer to a beginning of a buffer containing
  1572. /// an identifier.
  1573. /// @param identifier_len Identifier length.
  1574. /// @param stindex Statement index.
  1575. /// @param exchange Pointer to the exchange object used for the
  1576. /// particular query.
  1577. ///
  1578. /// @return Pointer to const instance of Host or null pointer if
  1579. /// no host found.
  1580. ConstHostPtr getHost(const SubnetID& subnet_id,
  1581. const Host::IdentifierType& identifier_type,
  1582. const uint8_t* identifier_begin,
  1583. const size_t identifier_len,
  1584. StatementIndex stindex,
  1585. boost::shared_ptr<MySqlHostExchange> exchange) const;
  1586. /// @brief Throws exception if database is read only.
  1587. ///
  1588. /// This method should be called by the methods which write to the
  1589. /// database. If the backend is operating in read-only mode this
  1590. /// method will throw exception.
  1591. ///
  1592. /// @throw DbReadOnly if backend is operating in read only mode.
  1593. void checkReadOnly() const;
  1594. /// @brief Pointer to the object representing an exchange which
  1595. /// can be used to retrieve hosts and DHCPv4 options.
  1596. boost::shared_ptr<MySqlHostWithOptionsExchange> host_exchange_;
  1597. /// @brief Pointer to an object representing an exchange which can
  1598. /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
  1599. boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
  1600. /// @brief Pointer to an object representing an exchange which can
  1601. /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
  1602. /// IPv6 reservations using a single query.
  1603. boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
  1604. /// @brief Pointer to an object representing an exchange which can
  1605. /// be used to insert new IPv6 reservation.
  1606. boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
  1607. /// @brief Pointer to an object representing an exchange which can
  1608. /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
  1609. /// or dhcp6_options table.
  1610. boost::shared_ptr<MySqlOptionExchange> host_option_exchange_;
  1611. /// @brief MySQL connection
  1612. MySqlConnection conn_;
  1613. /// @brief Indicates if the database is opened in read only mode.
  1614. bool is_readonly_;
  1615. };
  1616. /// @brief Array of tagged statements.
  1617. typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS>
  1618. TaggedStatementArray;
  1619. /// @brief Prepared MySQL statements used by the backend to insert and
  1620. /// retrieve hosts from the database.
  1621. TaggedStatementArray tagged_statements = { {
  1622. // Retrieves host information, IPv6 reservations and both DHCPv4 and
  1623. // DHCPv6 options associated with the host. The LEFT JOIN clause is used
  1624. // to retrieve information from 4 different tables using a single query.
  1625. // Hence, this query returns multiple rows for a single host.
  1626. {MySqlHostDataSourceImpl::GET_HOST_DHCPID,
  1627. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1628. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
  1629. "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1630. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1631. "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
  1632. "o4.persistent, "
  1633. "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
  1634. "o6.persistent, "
  1635. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1636. "r.dhcp6_iaid "
  1637. "FROM hosts AS h "
  1638. "LEFT JOIN dhcp4_options AS o4 "
  1639. "ON h.host_id = o4.host_id "
  1640. "LEFT JOIN dhcp6_options AS o6 "
  1641. "ON h.host_id = o6.host_id "
  1642. "LEFT JOIN ipv6_reservations AS r "
  1643. "ON h.host_id = r.host_id "
  1644. "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
  1645. "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
  1646. // Retrieves host information along with the DHCPv4 options associated with
  1647. // it. Left joining the dhcp4_options table results in multiple rows being
  1648. // returned for the same host. The host is retrieved by IPv4 address.
  1649. {MySqlHostDataSourceImpl::GET_HOST_ADDR,
  1650. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1651. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1652. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1653. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1654. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1655. "o.persistent "
  1656. "FROM hosts AS h "
  1657. "LEFT JOIN dhcp4_options AS o "
  1658. "ON h.host_id = o.host_id "
  1659. "WHERE ipv4_address = ? "
  1660. "ORDER BY h.host_id, o.option_id"},
  1661. // Retrieves host information and DHCPv4 options using subnet identifier
  1662. // and client's identifier. Left joining the dhcp4_options table results in
  1663. // multiple rows being returned for the same host.
  1664. {MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
  1665. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1666. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1667. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1668. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1669. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1670. "o.persistent "
  1671. "FROM hosts AS h "
  1672. "LEFT JOIN dhcp4_options AS o "
  1673. "ON h.host_id = o.host_id "
  1674. "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
  1675. " AND h.dhcp_identifier = ? "
  1676. "ORDER BY h.host_id, o.option_id"},
  1677. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1678. // associated with a host. The number of rows returned is a multiplication
  1679. // of number of IPv6 reservations and DHCPv6 options.
  1680. {MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
  1681. "SELECT h.host_id, h.dhcp_identifier, "
  1682. "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1683. "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1684. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1685. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1686. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1687. "o.persistent, "
  1688. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1689. "r.dhcp6_iaid "
  1690. "FROM hosts AS h "
  1691. "LEFT JOIN dhcp6_options AS o "
  1692. "ON h.host_id = o.host_id "
  1693. "LEFT JOIN ipv6_reservations AS r "
  1694. "ON h.host_id = r.host_id "
  1695. "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
  1696. "AND h.dhcp_identifier = ? "
  1697. "ORDER BY h.host_id, o.option_id, r.reservation_id"},
  1698. // Retrieves host information and DHCPv4 options for the host using subnet
  1699. // identifier and IPv4 reservation. Left joining the dhcp4_options table
  1700. // results in multiple rows being returned for the host. The number of
  1701. // rows depends on the number of options defined for the host.
  1702. {MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
  1703. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1704. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1705. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1706. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1707. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1708. "o.persistent "
  1709. "FROM hosts AS h "
  1710. "LEFT JOIN dhcp4_options AS o "
  1711. "ON h.host_id = o.host_id "
  1712. "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
  1713. "ORDER BY h.host_id, o.option_id"},
  1714. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1715. // associated with a host using prefix and prefix length. This query
  1716. // returns host information for a single host. However, multiple rows
  1717. // are returned due to left joining IPv6 reservations and DHCPv6 options.
  1718. // The number of rows returned is multiplication of number of existing
  1719. // IPv6 reservations and DHCPv6 options.
  1720. {MySqlHostDataSourceImpl::GET_HOST_PREFIX,
  1721. "SELECT h.host_id, h.dhcp_identifier, "
  1722. "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1723. "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1724. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1725. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1726. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1727. "o.persistent, "
  1728. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1729. "r.dhcp6_iaid "
  1730. "FROM hosts AS h "
  1731. "LEFT JOIN dhcp6_options AS o "
  1732. "ON h.host_id = o.host_id "
  1733. "LEFT JOIN ipv6_reservations AS r "
  1734. "ON h.host_id = r.host_id "
  1735. "WHERE h.host_id = "
  1736. "(SELECT host_id FROM ipv6_reservations "
  1737. "WHERE address = ? AND prefix_len = ?) "
  1738. "ORDER BY h.host_id, o.option_id, r.reservation_id"},
  1739. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1740. // associated with a host using subnet id and prefix. This query
  1741. // returns host information for a single host. However, multiple rows
  1742. // are returned due to left joining IPv6 reservations and DHCPv6 options.
  1743. // The number of rows returned is multiplication of number of existing
  1744. // IPv6 reservations and DHCPv6 options.
  1745. {MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
  1746. "SELECT h.host_id, h.dhcp_identifier, "
  1747. "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1748. "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1749. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1750. "h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
  1751. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1752. "o.persistent, "
  1753. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1754. "r.dhcp6_iaid "
  1755. "FROM hosts AS h "
  1756. "LEFT JOIN dhcp6_options AS o "
  1757. "ON h.host_id = o.host_id "
  1758. "LEFT JOIN ipv6_reservations AS r "
  1759. "ON h.host_id = r.host_id "
  1760. "WHERE h.dhcp6_subnet_id = ? AND r.address = ? "
  1761. "ORDER BY h.host_id, o.option_id, r.reservation_id"},
  1762. // Retrieves MySQL schema version.
  1763. {MySqlHostDataSourceImpl::GET_VERSION,
  1764. "SELECT version, minor FROM schema_version"},
  1765. // Inserts a host into the 'hosts' table.
  1766. {MySqlHostDataSourceImpl::INSERT_HOST,
  1767. "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
  1768. "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
  1769. "dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, "
  1770. "dhcp4_server_hostname, dhcp4_boot_file_name) "
  1771. "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
  1772. // Inserts a single IPv6 reservation into 'reservations' table.
  1773. {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
  1774. "INSERT INTO ipv6_reservations(address, prefix_len, type, "
  1775. "dhcp6_iaid, host_id) "
  1776. "VALUES (?,?,?,?,?)"},
  1777. // Inserts a single DHCPv4 option into 'dhcp4_options' table.
  1778. // Using fixed scope_id = 3, which associates an option with host.
  1779. {MySqlHostDataSourceImpl::INSERT_V4_OPTION,
  1780. "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
  1781. "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
  1782. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
  1783. // Inserts a single DHCPv6 option into 'dhcp6_options' table.
  1784. // Using fixed scope_id = 3, which associates an option with host.
  1785. {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
  1786. "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
  1787. "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
  1788. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
  1789. {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
  1790. "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
  1791. {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
  1792. "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
  1793. "AND dhcp_identifier = ?"},
  1794. {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
  1795. "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
  1796. "AND dhcp_identifier = ?"}
  1797. }
  1798. };
  1799. MySqlHostDataSourceImpl::
  1800. MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
  1801. : host_exchange_(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY)),
  1802. host_ipv6_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY)),
  1803. host_ipv46_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::
  1804. DHCP4_AND_DHCP6)),
  1805. host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
  1806. host_option_exchange_(new MySqlOptionExchange()),
  1807. conn_(parameters),
  1808. is_readonly_(false) {
  1809. // Open the database.
  1810. conn_.openDatabase();
  1811. // Enable autocommit. In case transaction is explicitly used, this
  1812. // setting will be overwritten for the transaction. However, there are
  1813. // cases when lack of autocommit could cause transactions to hang
  1814. // until commit or rollback is explicitly called. This already
  1815. // caused issues for some unit tests which were unable to cleanup
  1816. // the database after the test because of pending transactions.
  1817. // Use of autocommit will eliminate this problem.
  1818. my_bool result = mysql_autocommit(conn_.mysql_, 1);
  1819. if (result != 0) {
  1820. isc_throw(DbOperationError, mysql_error(conn_.mysql_));
  1821. }
  1822. // Prepare query statements. Those are will be only used to retrieve
  1823. // information from the database, so they can be used even if the
  1824. // database is read only for the current user.
  1825. conn_.prepareStatements(tagged_statements.begin(),
  1826. tagged_statements.begin() + WRITE_STMTS_BEGIN);
  1827. // Check if the backend is explicitly configured to operate with
  1828. // read only access to the database.
  1829. is_readonly_ = conn_.configuredReadOnly();
  1830. // If we are using read-write mode for the database we also prepare
  1831. // statements for INSERTS etc.
  1832. if (!is_readonly_) {
  1833. // Prepare statements for writing to the database, e.g. INSERT.
  1834. conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
  1835. tagged_statements.end());
  1836. } else {
  1837. LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY);
  1838. }
  1839. }
  1840. MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
  1841. // Free up the prepared statements, ignoring errors. (What would we do
  1842. // about them? We're destroying this object and are not really concerned
  1843. // with errors on a database connection that is about to go away.)
  1844. for (int i = 0; i < conn_.statements_.size(); ++i) {
  1845. if (conn_.statements_[i] != NULL) {
  1846. (void) mysql_stmt_close(conn_.statements_[i]);
  1847. conn_.statements_[i] = NULL;
  1848. }
  1849. }
  1850. // There is no need to close the database in this destructor: it is
  1851. // closed in the destructor of the mysql_ member variable.
  1852. }
  1853. void
  1854. MySqlHostDataSourceImpl::addStatement(StatementIndex stindex,
  1855. std::vector<MYSQL_BIND>& bind) {
  1856. // Bind the parameters to the statement
  1857. int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
  1858. checkError(status, stindex, "unable to bind parameters");
  1859. // Execute the statement
  1860. status = mysql_stmt_execute(conn_.statements_[stindex]);
  1861. if (status != 0) {
  1862. // Failure: check for the special case of duplicate entry.
  1863. if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
  1864. isc_throw(DuplicateEntry, "Database duplicate entry error");
  1865. }
  1866. checkError(status, stindex, "unable to execute");
  1867. }
  1868. }
  1869. bool
  1870. MySqlHostDataSourceImpl::delStatement(StatementIndex stindex,
  1871. MYSQL_BIND* bind) {
  1872. // Bind the parameters to the statement
  1873. int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
  1874. checkError(status, stindex, "unable to bind parameters");
  1875. // Execute the statement
  1876. status = mysql_stmt_execute(conn_.statements_[stindex]);
  1877. if (status != 0) {
  1878. checkError(status, stindex, "unable to execute");
  1879. }
  1880. // Let's check how many hosts were deleted.
  1881. my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
  1882. return (numrows != 0);
  1883. }
  1884. void
  1885. MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
  1886. const HostID& id) {
  1887. std::vector<MYSQL_BIND> bind =
  1888. host_ipv6_reservation_exchange_->createBindForSend(resv, id);
  1889. addStatement(INSERT_V6_RESRV, bind);
  1890. }
  1891. void
  1892. MySqlHostDataSourceImpl::addOption(const StatementIndex& stindex,
  1893. const OptionDescriptor& opt_desc,
  1894. const std::string& opt_space,
  1895. const OptionalValue<SubnetID>& subnet_id,
  1896. const HostID& id) {
  1897. std::vector<MYSQL_BIND> bind =
  1898. host_option_exchange_->createBindForSend(opt_desc, opt_space,
  1899. subnet_id, id);
  1900. addStatement(stindex, bind);
  1901. }
  1902. void
  1903. MySqlHostDataSourceImpl::addOptions(const StatementIndex& stindex,
  1904. const ConstCfgOptionPtr& options_cfg,
  1905. const uint64_t host_id) {
  1906. // Get option space names and vendor space names and combine them within a
  1907. // single list.
  1908. std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
  1909. std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
  1910. option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
  1911. vendor_spaces.end());
  1912. // For each option space retrieve all options and insert them into the
  1913. // database.
  1914. for (std::list<std::string>::const_iterator space = option_spaces.begin();
  1915. space != option_spaces.end(); ++space) {
  1916. OptionContainerPtr options = options_cfg->getAll(*space);
  1917. if (options && !options->empty()) {
  1918. for (OptionContainer::const_iterator opt = options->begin();
  1919. opt != options->end(); ++opt) {
  1920. addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
  1921. host_id);
  1922. }
  1923. }
  1924. }
  1925. }
  1926. void
  1927. MySqlHostDataSourceImpl::
  1928. checkError(const int status, const StatementIndex index,
  1929. const char* what) const {
  1930. conn_.checkError(status, index, what);
  1931. }
  1932. void
  1933. MySqlHostDataSourceImpl::
  1934. getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
  1935. boost::shared_ptr<MySqlHostExchange> exchange,
  1936. ConstHostCollection& result, bool single) const {
  1937. // Bind the selection parameters to the statement
  1938. int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
  1939. checkError(status, stindex, "unable to bind WHERE clause parameter");
  1940. // Set up the MYSQL_BIND array for the data being returned and bind it to
  1941. // the statement.
  1942. std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
  1943. status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
  1944. checkError(status, stindex, "unable to bind SELECT clause parameters");
  1945. // Execute the statement
  1946. status = mysql_stmt_execute(conn_.statements_[stindex]);
  1947. checkError(status, stindex, "unable to execute");
  1948. // Ensure that all the lease information is retrieved in one go to avoid
  1949. // overhead of going back and forth between client and server.
  1950. status = mysql_stmt_store_result(conn_.statements_[stindex]);
  1951. checkError(status, stindex, "unable to set up for storing all results");
  1952. // Set up the fetch "release" object to release resources associated
  1953. // with the call to mysql_stmt_fetch when this method exits, then
  1954. // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
  1955. // successful data fetch.
  1956. MySqlFreeResult fetch_release(conn_.statements_[stindex]);
  1957. while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) ==
  1958. MLM_MYSQL_FETCH_SUCCESS) {
  1959. try {
  1960. exchange->processFetchedData(result);
  1961. } catch (const isc::BadValue& ex) {
  1962. // Rethrow the exception with a bit more data.
  1963. isc_throw(BadValue, ex.what() << ". Statement is <" <<
  1964. conn_.text_statements_[stindex] << ">");
  1965. }
  1966. if (single && (result.size() > 1)) {
  1967. isc_throw(MultipleRecords, "multiple records were found in the "
  1968. "database where only one was expected for query "
  1969. << conn_.text_statements_[stindex]);
  1970. }
  1971. }
  1972. // How did the fetch end?
  1973. // If mysql_stmt_fetch return value is equal to 1 an error occurred.
  1974. if (status == MLM_MYSQL_FETCH_FAILURE) {
  1975. // Error - unable to fetch results
  1976. checkError(status, stindex, "unable to fetch results");
  1977. } else if (status == MYSQL_DATA_TRUNCATED) {
  1978. // Data truncated - throw an exception indicating what was at fault
  1979. isc_throw(DataTruncated, conn_.text_statements_[stindex]
  1980. << " returned truncated data: columns affected are "
  1981. << exchange->getErrorColumns());
  1982. }
  1983. }
  1984. ConstHostPtr
  1985. MySqlHostDataSourceImpl::
  1986. getHost(const SubnetID& subnet_id,
  1987. const Host::IdentifierType& identifier_type,
  1988. const uint8_t* identifier_begin,
  1989. const size_t identifier_len,
  1990. StatementIndex stindex,
  1991. boost::shared_ptr<MySqlHostExchange> exchange) const {
  1992. // Set up the WHERE clause value
  1993. MYSQL_BIND inbind[3];
  1994. memset(inbind, 0, sizeof(inbind));
  1995. uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
  1996. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  1997. inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
  1998. inbind[0].is_unsigned = MLM_TRUE;
  1999. // Identifier value.
  2000. std::vector<char> identifier_vec(identifier_begin,
  2001. identifier_begin + identifier_len);
  2002. unsigned long length = identifier_vec.size();
  2003. inbind[2].buffer_type = MYSQL_TYPE_BLOB;
  2004. inbind[2].buffer = &identifier_vec[0];
  2005. inbind[2].buffer_length = length;
  2006. inbind[2].length = &length;
  2007. // Identifier type.
  2008. char identifier_type_copy = static_cast<char>(identifier_type);
  2009. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2010. inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
  2011. inbind[1].is_unsigned = MLM_TRUE;
  2012. ConstHostCollection collection;
  2013. getHostCollection(stindex, inbind, exchange, collection, true);
  2014. // Return single record if present, else clear the host.
  2015. ConstHostPtr result;
  2016. if (!collection.empty())
  2017. result = *collection.begin();
  2018. return (result);
  2019. }
  2020. void
  2021. MySqlHostDataSourceImpl::checkReadOnly() const {
  2022. if (is_readonly_) {
  2023. isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
  2024. " operate in read only mode");
  2025. }
  2026. }
  2027. MySqlHostDataSource::
  2028. MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
  2029. : impl_(new MySqlHostDataSourceImpl(parameters)) {
  2030. }
  2031. MySqlHostDataSource::~MySqlHostDataSource() {
  2032. delete impl_;
  2033. }
  2034. void
  2035. MySqlHostDataSource::add(const HostPtr& host) {
  2036. // If operating in read-only mode, throw exception.
  2037. impl_->checkReadOnly();
  2038. // Initiate MySQL transaction as we will have to make multiple queries
  2039. // to insert host information into multiple tables. If that fails on
  2040. // any stage, the transaction will be rolled back by the destructor of
  2041. // the MySqlTransaction class.
  2042. MySqlTransaction transaction(impl_->conn_);
  2043. // Create the MYSQL_BIND array for the host
  2044. std::vector<MYSQL_BIND> bind = impl_->host_exchange_->createBindForSend(host);
  2045. // ... and insert the host.
  2046. impl_->addStatement(MySqlHostDataSourceImpl::INSERT_HOST, bind);
  2047. // Gets the last inserted hosts id
  2048. uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
  2049. // Insert DHCPv4 options.
  2050. ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
  2051. if (cfg_option4) {
  2052. impl_->addOptions(MySqlHostDataSourceImpl::INSERT_V4_OPTION,
  2053. cfg_option4, host_id);
  2054. }
  2055. // Insert DHCPv6 options.
  2056. ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
  2057. if (cfg_option6) {
  2058. impl_->addOptions(MySqlHostDataSourceImpl::INSERT_V6_OPTION,
  2059. cfg_option6, host_id);
  2060. }
  2061. // Insert IPv6 reservations.
  2062. IPv6ResrvRange v6resv = host->getIPv6Reservations();
  2063. if (std::distance(v6resv.first, v6resv.second) > 0) {
  2064. for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
  2065. ++resv) {
  2066. impl_->addResv(resv->second, host_id);
  2067. }
  2068. }
  2069. // Everything went fine, so explicitly commit the transaction.
  2070. transaction.commit();
  2071. }
  2072. bool
  2073. MySqlHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
  2074. // If operating in read-only mode, throw exception.
  2075. impl_->checkReadOnly();
  2076. if (addr.isV4()) {
  2077. // Set up the WHERE clause value
  2078. MYSQL_BIND inbind[2];
  2079. uint32_t subnet = subnet_id;
  2080. memset(inbind, 0, sizeof(inbind));
  2081. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2082. inbind[0].buffer = reinterpret_cast<char*>(&subnet);
  2083. inbind[0].is_unsigned = MLM_TRUE;
  2084. uint32_t addr4 = addr.toUint32();
  2085. inbind[1].buffer_type = MYSQL_TYPE_LONG;
  2086. inbind[1].buffer = reinterpret_cast<char*>(&addr4);
  2087. inbind[1].is_unsigned = MLM_TRUE;
  2088. ConstHostCollection collection;
  2089. return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
  2090. }
  2091. // v6
  2092. ConstHostPtr host = get6(subnet_id, addr);
  2093. if (!host) {
  2094. return (false);
  2095. }
  2096. // Ok, there is a host. Let's delete it.
  2097. return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
  2098. host->getIdentifier().size());
  2099. }
  2100. bool
  2101. MySqlHostDataSource::del4(const SubnetID& subnet_id,
  2102. const Host::IdentifierType& identifier_type,
  2103. const uint8_t* identifier_begin, const size_t identifier_len) {
  2104. // If operating in read-only mode, throw exception.
  2105. impl_->checkReadOnly();
  2106. // Set up the WHERE clause value
  2107. MYSQL_BIND inbind[3];
  2108. // subnet-id
  2109. memset(inbind, 0, sizeof(inbind));
  2110. uint32_t subnet = static_cast<uint32_t>(subnet_id);
  2111. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2112. inbind[0].buffer = reinterpret_cast<char*>(&subnet);
  2113. inbind[0].is_unsigned = MLM_TRUE;
  2114. // identifier type
  2115. char identifier_type_copy = static_cast<char>(identifier_type);
  2116. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2117. inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
  2118. inbind[1].is_unsigned = MLM_TRUE;
  2119. // identifier value
  2120. std::vector<char> identifier_vec(identifier_begin,
  2121. identifier_begin + identifier_len);
  2122. unsigned long length = identifier_vec.size();
  2123. inbind[2].buffer_type = MYSQL_TYPE_BLOB;
  2124. inbind[2].buffer = &identifier_vec[0];
  2125. inbind[2].buffer_length = length;
  2126. inbind[2].length = &length;
  2127. ConstHostCollection collection;
  2128. return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
  2129. }
  2130. bool
  2131. MySqlHostDataSource::del6(const SubnetID& subnet_id,
  2132. const Host::IdentifierType& identifier_type,
  2133. const uint8_t* identifier_begin, const size_t identifier_len) {
  2134. // If operating in read-only mode, throw exception.
  2135. impl_->checkReadOnly();
  2136. // Set up the WHERE clause value
  2137. MYSQL_BIND inbind[3];
  2138. // subnet-id
  2139. memset(inbind, 0, sizeof(inbind));
  2140. uint32_t subnet = static_cast<uint32_t>(subnet_id);
  2141. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2142. inbind[0].buffer = reinterpret_cast<char*>(&subnet);
  2143. inbind[0].is_unsigned = MLM_TRUE;
  2144. // identifier type
  2145. char identifier_type_copy = static_cast<char>(identifier_type);
  2146. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2147. inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
  2148. inbind[1].is_unsigned = MLM_TRUE;
  2149. // identifier value
  2150. std::vector<char> identifier_vec(identifier_begin,
  2151. identifier_begin + identifier_len);
  2152. unsigned long length = identifier_vec.size();
  2153. inbind[2].buffer_type = MYSQL_TYPE_BLOB;
  2154. inbind[2].buffer = &identifier_vec[0];
  2155. inbind[2].buffer_length = length;
  2156. inbind[2].length = &length;
  2157. ConstHostCollection collection;
  2158. return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
  2159. }
  2160. ConstHostCollection
  2161. MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
  2162. const DuidPtr& duid) const {
  2163. if (duid){
  2164. return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
  2165. duid->getDuid().size()));
  2166. } else if (hwaddr) {
  2167. return (getAll(Host::IDENT_HWADDR,
  2168. &hwaddr->hwaddr_[0],
  2169. hwaddr->hwaddr_.size()));
  2170. }
  2171. return (ConstHostCollection());
  2172. }
  2173. ConstHostCollection
  2174. MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
  2175. const uint8_t* identifier_begin,
  2176. const size_t identifier_len) const {
  2177. // Set up the WHERE clause value
  2178. MYSQL_BIND inbind[2];
  2179. memset(inbind, 0, sizeof(inbind));
  2180. // Identifier type.
  2181. char identifier_type_copy = static_cast<char>(identifier_type);
  2182. inbind[1].buffer = &identifier_type_copy;
  2183. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2184. inbind[1].is_unsigned = MLM_TRUE;
  2185. // Identifier value.
  2186. std::vector<char> identifier_vec(identifier_begin,
  2187. identifier_begin + identifier_len);
  2188. unsigned long int length = identifier_vec.size();
  2189. inbind[0].buffer_type = MYSQL_TYPE_BLOB;
  2190. inbind[0].buffer = &identifier_vec[0];
  2191. inbind[0].buffer_length = length;
  2192. inbind[0].length = &length;
  2193. ConstHostCollection result;
  2194. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_DHCPID, inbind,
  2195. impl_->host_ipv46_exchange_,
  2196. result, false);
  2197. return (result);
  2198. }
  2199. ConstHostCollection
  2200. MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
  2201. // Set up the WHERE clause value
  2202. MYSQL_BIND inbind[1];
  2203. memset(inbind, 0, sizeof(inbind));
  2204. uint32_t addr4 = address.toUint32();
  2205. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2206. inbind[0].buffer = reinterpret_cast<char*>(&addr4);
  2207. inbind[0].is_unsigned = MLM_TRUE;
  2208. ConstHostCollection result;
  2209. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind,
  2210. impl_->host_exchange_, result, false);
  2211. return (result);
  2212. }
  2213. ConstHostPtr
  2214. MySqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
  2215. const DuidPtr& duid) const {
  2216. /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
  2217. if (hwaddr && duid) {
  2218. isc_throw(BadValue, "MySQL host data source get4() called with both"
  2219. " hwaddr and duid, only one of them is allowed");
  2220. }
  2221. if (!hwaddr && !duid) {
  2222. isc_throw(BadValue, "MySQL host data source get4() called with "
  2223. "neither hwaddr or duid specified, one of them is required");
  2224. }
  2225. // Choosing one of the identifiers
  2226. if (hwaddr) {
  2227. return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  2228. hwaddr->hwaddr_.size()));
  2229. } else if (duid) {
  2230. return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  2231. duid->getDuid().size()));
  2232. }
  2233. return (ConstHostPtr());
  2234. }
  2235. ConstHostPtr
  2236. MySqlHostDataSource::get4(const SubnetID& subnet_id,
  2237. const Host::IdentifierType& identifier_type,
  2238. const uint8_t* identifier_begin,
  2239. const size_t identifier_len) const {
  2240. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  2241. identifier_len, MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
  2242. impl_->host_exchange_));
  2243. }
  2244. ConstHostPtr
  2245. MySqlHostDataSource::get4(const SubnetID& subnet_id,
  2246. const asiolink::IOAddress& address) const {
  2247. /// @todo: check that address is really v4, not v6.
  2248. // Set up the WHERE clause value
  2249. MYSQL_BIND inbind[2];
  2250. uint32_t subnet = subnet_id;
  2251. memset(inbind, 0, sizeof(inbind));
  2252. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2253. inbind[0].buffer = reinterpret_cast<char*>(&subnet);
  2254. inbind[0].is_unsigned = MLM_TRUE;
  2255. uint32_t addr4 = address.toUint32();
  2256. inbind[1].buffer_type = MYSQL_TYPE_LONG;
  2257. inbind[1].buffer = reinterpret_cast<char*>(&addr4);
  2258. inbind[1].is_unsigned = MLM_TRUE;
  2259. ConstHostCollection collection;
  2260. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
  2261. inbind, impl_->host_exchange_, collection, true);
  2262. // Return single record if present, else clear the host.
  2263. ConstHostPtr result;
  2264. if (!collection.empty())
  2265. result = *collection.begin();
  2266. return (result);
  2267. }
  2268. ConstHostPtr
  2269. MySqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
  2270. const HWAddrPtr& hwaddr) const {
  2271. /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
  2272. if (hwaddr && duid) {
  2273. isc_throw(BadValue, "MySQL host data source get6() called with both"
  2274. " hwaddr and duid, only one of them is allowed");
  2275. }
  2276. if (!hwaddr && !duid) {
  2277. isc_throw(BadValue, "MySQL host data source get6() called with "
  2278. "neither hwaddr or duid specified, one of them is required");
  2279. }
  2280. // Choosing one of the identifiers
  2281. if (hwaddr) {
  2282. return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  2283. hwaddr->hwaddr_.size()));
  2284. } else if (duid) {
  2285. return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  2286. duid->getDuid().size()));
  2287. }
  2288. return (ConstHostPtr());
  2289. }
  2290. ConstHostPtr
  2291. MySqlHostDataSource::get6(const SubnetID& subnet_id,
  2292. const Host::IdentifierType& identifier_type,
  2293. const uint8_t* identifier_begin,
  2294. const size_t identifier_len) const {
  2295. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  2296. identifier_len, MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
  2297. impl_->host_ipv6_exchange_));
  2298. }
  2299. ConstHostPtr
  2300. MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
  2301. const uint8_t prefix_len) const {
  2302. /// @todo: Check that prefix is v6 address, not v4.
  2303. // Set up the WHERE clause value
  2304. MYSQL_BIND inbind[2];
  2305. memset(inbind, 0, sizeof(inbind));
  2306. std::string addr6 = prefix.toText();
  2307. unsigned long addr6_length = addr6.size();
  2308. inbind[0].buffer_type = MYSQL_TYPE_BLOB;
  2309. inbind[0].buffer = reinterpret_cast<char*>
  2310. (const_cast<char*>(addr6.c_str()));
  2311. inbind[0].length = &addr6_length;
  2312. inbind[0].buffer_length = addr6_length;
  2313. uint8_t tmp = prefix_len;
  2314. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2315. inbind[1].buffer = reinterpret_cast<char*>(&tmp);
  2316. inbind[1].is_unsigned = MLM_TRUE;
  2317. ConstHostCollection collection;
  2318. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_PREFIX,
  2319. inbind, impl_->host_ipv6_exchange_,
  2320. collection, true);
  2321. // Return single record if present, else clear the host.
  2322. ConstHostPtr result;
  2323. if (!collection.empty()) {
  2324. result = *collection.begin();
  2325. }
  2326. return (result);
  2327. }
  2328. ConstHostPtr
  2329. MySqlHostDataSource::get6(const SubnetID& subnet_id,
  2330. const asiolink::IOAddress& address) const {
  2331. // Set up the WHERE clause value
  2332. MYSQL_BIND inbind[2];
  2333. memset(inbind, 0, sizeof(inbind));
  2334. uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
  2335. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  2336. inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
  2337. inbind[0].is_unsigned = MLM_TRUE;
  2338. std::string addr6 = address.toText();
  2339. unsigned long addr6_length = addr6.size();
  2340. inbind[1].buffer_type = MYSQL_TYPE_BLOB;
  2341. inbind[1].buffer = reinterpret_cast<char*>
  2342. (const_cast<char*>(addr6.c_str()));
  2343. inbind[1].length = &addr6_length;
  2344. inbind[1].buffer_length = addr6_length;
  2345. ConstHostCollection collection;
  2346. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
  2347. inbind, impl_->host_ipv6_exchange_,
  2348. collection, true);
  2349. // Return single record if present, else clear the host.
  2350. ConstHostPtr result;
  2351. if (!collection.empty()) {
  2352. result = *collection.begin();
  2353. }
  2354. return (result);
  2355. }
  2356. // Miscellaneous database methods.
  2357. std::string MySqlHostDataSource::getName() const {
  2358. std::string name = "";
  2359. try {
  2360. name = impl_->conn_.getParameter("name");
  2361. } catch (...) {
  2362. // Return an empty name
  2363. }
  2364. return (name);
  2365. }
  2366. std::string MySqlHostDataSource::getDescription() const {
  2367. return (std::string("Host data source that stores host information"
  2368. "in MySQL database"));
  2369. }
  2370. std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
  2371. const MySqlHostDataSourceImpl::StatementIndex stindex =
  2372. MySqlHostDataSourceImpl::GET_VERSION;
  2373. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
  2374. DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
  2375. uint32_t major; // Major version number
  2376. uint32_t minor; // Minor version number
  2377. // Execute the prepared statement
  2378. int status = mysql_stmt_execute(impl_->conn_.statements_[stindex]);
  2379. if (status != 0) {
  2380. isc_throw(DbOperationError, "unable to execute <"
  2381. << impl_->conn_.text_statements_[stindex]
  2382. << "> - reason: " << mysql_error(impl_->conn_.mysql_));
  2383. }
  2384. // Bind the output of the statement to the appropriate variables.
  2385. MYSQL_BIND bind[2];
  2386. memset(bind, 0, sizeof(bind));
  2387. bind[0].buffer_type = MYSQL_TYPE_LONG;
  2388. bind[0].is_unsigned = 1;
  2389. bind[0].buffer = &major;
  2390. bind[0].buffer_length = sizeof(major);
  2391. bind[1].buffer_type = MYSQL_TYPE_LONG;
  2392. bind[1].is_unsigned = 1;
  2393. bind[1].buffer = &minor;
  2394. bind[1].buffer_length = sizeof(minor);
  2395. status = mysql_stmt_bind_result(impl_->conn_.statements_[stindex], bind);
  2396. if (status != 0) {
  2397. isc_throw(DbOperationError, "unable to bind result set: "
  2398. << mysql_error(impl_->conn_.mysql_));
  2399. }
  2400. // Fetch the data and set up the "release" object to release associated
  2401. // resources when this method exits then retrieve the data.
  2402. // mysql_stmt_fetch return value other than 0 means error occurrence.
  2403. MySqlFreeResult fetch_release(impl_->conn_.statements_[stindex]);
  2404. status = mysql_stmt_fetch(impl_->conn_.statements_[stindex]);
  2405. if (status != 0) {
  2406. isc_throw(DbOperationError, "unable to obtain result set: "
  2407. << mysql_error(impl_->conn_.mysql_));
  2408. }
  2409. return (std::make_pair(major, minor));
  2410. }
  2411. void
  2412. MySqlHostDataSource::commit() {
  2413. // If operating in read-only mode, throw exception.
  2414. impl_->checkReadOnly();
  2415. impl_->conn_.commit();
  2416. }
  2417. void
  2418. MySqlHostDataSource::rollback() {
  2419. // If operating in read-only mode, throw exception.
  2420. impl_->checkReadOnly();
  2421. impl_->conn_.rollback();
  2422. }
  2423. }; // end of isc::dhcp namespace
  2424. }; // end of isc namespace