You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

556 lines
17 KiB

10 years ago
  1. /* -*- coding: utf-8 -*-
  2. * ----------------------------------------------------------------------
  3. * Copyright © 2010-2013, RedJack, LLC.
  4. * All rights reserved.
  5. *
  6. * Please see the LICENSE.txt file in this distribution for license
  7. * details.
  8. * ----------------------------------------------------------------------
  9. */
  10. #include <libcork/core.h>
  11. #include <libcork/helpers/errors.h>
  12. #include "ipset/bdd/nodes.h"
  13. #include "ipset/logging.h"
  14. /*-----------------------------------------------------------------------
  15. * Generic saving logic
  16. */
  17. /**
  18. * On disk, we use a different node ID scheme than we do in memory.
  19. * Terminal node IDs are non-negative, and are equal to the terminal
  20. * value. Nonterminal node IDs are negative, starting with -1.
  21. * Nonterminal -1 appears first on disk, then nonterminal -2, and so
  22. * on.
  23. */
  24. typedef int serialized_id;
  25. /* forward declaration */
  26. struct save_data;
  27. /**
  28. * A callback that outputs any necessary header. Should return an int
  29. * status code indicating whether the write was successful.
  30. */
  31. typedef int
  32. (*write_header_func)(struct save_data *save_data,
  33. struct ipset_node_cache *cache,
  34. ipset_node_id root);
  35. /**
  36. * A callback that outputs any necessary footer. Should return an int
  37. * status code indicating whether the write was successful.
  38. */
  39. typedef int
  40. (*write_footer_func)(struct save_data *save_data,
  41. struct ipset_node_cache *cache,
  42. ipset_node_id root);
  43. /**
  44. * A callback that actually outputs a terminal node to disk. Should
  45. * return an int status code indicating whether the write was successful.
  46. */
  47. typedef int
  48. (*write_terminal_func)(struct save_data *save_data,
  49. ipset_value terminal_value);
  50. /**
  51. * A callback that actually outputs a nonterminal node to disk.
  52. * Should return an int status code indicating whether the write was
  53. * successful.
  54. */
  55. typedef int
  56. (*write_nonterminal_func)(struct save_data *save_data,
  57. serialized_id serialized_node,
  58. ipset_variable variable,
  59. serialized_id serialized_low,
  60. serialized_id serialized_high);
  61. /**
  62. * A helper struct containing all of the persistent data items needed
  63. * during the execution of a save.
  64. */
  65. struct save_data {
  66. /* The node cache that we're saving nodes from. */
  67. struct ipset_node_cache *cache;
  68. /* The output stream to save the data to. */
  69. struct cork_stream_consumer *stream;
  70. /* The cache of serialized IDs for any nonterminals that we've
  71. * encountered so far. */
  72. struct cork_hash_table *serialized_ids;
  73. /* The serialized ID to use for the next nonterminal that we
  74. * encounter. */
  75. serialized_id next_serialized_id;
  76. /* The callback used to write the file header to the stream. */
  77. write_header_func write_header;
  78. /* The callback used to write the file footer to the stream. */
  79. write_footer_func write_footer;
  80. /* The callback used to write terminals to the stream. */
  81. write_terminal_func write_terminal;
  82. /* The callback used to write nonterminals to the stream. */
  83. write_nonterminal_func write_nonterminal;
  84. /* A pointer to any additional data needed by the callbacks. */
  85. void *user_data;
  86. };
  87. /**
  88. * A helper function for ipset_node_save(). Outputs a nonterminal
  89. * node in a BDD tree, if we haven't done so already. Ensures that
  90. * the children of the nonterminal are output before the nonterminal
  91. * is. Returns the serialized ID of this node.
  92. */
  93. static int
  94. save_visit_node(struct save_data *save_data,
  95. ipset_node_id node_id, serialized_id *dest)
  96. {
  97. /* Check whether we've already serialized this node. */
  98. struct cork_hash_table_entry *entry;
  99. bool is_new;
  100. entry = cork_hash_table_get_or_create
  101. (save_data->serialized_ids, (void *) (uintptr_t) node_id, &is_new);
  102. if (!is_new) {
  103. *dest = (intptr_t) entry->value;
  104. return 0;
  105. } else {
  106. if (ipset_node_get_type(node_id) == IPSET_TERMINAL_NODE) {
  107. /* For terminals, there isn't really anything to do — we
  108. * just output the terminal node and use its value as the
  109. * serialized ID. */
  110. ipset_value value = ipset_terminal_value(node_id);
  111. DEBUG("Writing terminal(%d)", value);
  112. rii_check(save_data->write_terminal(save_data, value));
  113. entry->value = (void *) (intptr_t) value;
  114. *dest = value;
  115. return 0;
  116. } else {
  117. /* For nonterminals, we drill down into the node's children
  118. * first, then output the nonterminal node. */
  119. struct ipset_node *node =
  120. ipset_node_cache_get_nonterminal(save_data->cache, node_id);
  121. DEBUG("Visiting node %u nonterminal(x%u? %u: %u)",
  122. node_id, node->variable, node->high, node->low);
  123. /* Output the node's nonterminal children before we output
  124. * the node itself. */
  125. serialized_id serialized_low;
  126. serialized_id serialized_high;
  127. rii_check(save_visit_node(save_data, node->low, &serialized_low));
  128. rii_check(save_visit_node(save_data, node->high, &serialized_high));
  129. /* Output the nonterminal */
  130. serialized_id result = save_data->next_serialized_id--;
  131. DEBUG("Writing node %u as serialized node %d = (x%u? %d: %d)",
  132. node_id, result,
  133. node->variable, serialized_low, serialized_high);
  134. entry->value = (void *) (intptr_t) result;
  135. *dest = result;
  136. return save_data->write_nonterminal
  137. (save_data, result, node->variable,
  138. serialized_low, serialized_high);
  139. }
  140. }
  141. }
  142. static int
  143. save_bdd(struct save_data *save_data,
  144. struct ipset_node_cache *cache, ipset_node_id root)
  145. {
  146. /* First, output the file header. */
  147. DEBUG("Writing file header");
  148. rii_check(save_data->write_header(save_data, cache, root));
  149. /* The serialized node IDs are different than the in-memory node
  150. * IDs. This means that, for our nonterminal nodes, we need a
  151. * mapping from internal node ID to serialized node ID. */
  152. DEBUG("Creating file caches");
  153. save_data->serialized_ids = cork_pointer_hash_table_new(0, 0);
  154. save_data->next_serialized_id = -1;
  155. /* Trace down through the BDD tree, outputting each terminal and
  156. * nonterminal node as they're encountered. */
  157. DEBUG("Writing nodes");
  158. serialized_id last_serialized_id;
  159. ei_check(save_visit_node(save_data, root, &last_serialized_id));
  160. /* Finally, output the file footer and cleanup. */
  161. DEBUG("Writing file footer");
  162. ei_check(save_data->write_footer(save_data, cache, root));
  163. DEBUG("Freeing file caches");
  164. cork_hash_table_free(save_data->serialized_ids);
  165. return 0;
  166. error:
  167. /* If there's an error, clean up the objects that we've created
  168. * before returning. */
  169. cork_hash_table_free(save_data->serialized_ids);
  170. return -1;
  171. }
  172. /*-----------------------------------------------------------------------
  173. * Helper functions
  174. */
  175. /**
  176. * Write a NUL-terminated string to a stream. If we can't write the
  177. * string for some reason, return an error.
  178. */
  179. static int
  180. write_string(struct cork_stream_consumer *stream, const char *str)
  181. {
  182. size_t len = strlen(str);
  183. return cork_stream_consumer_data(stream, str, len, false);
  184. }
  185. /**
  186. * Write a big-endian uint8 to a stream. If we can't write the
  187. * integer for some reason, return an error.
  188. */
  189. static int
  190. write_uint8(struct cork_stream_consumer *stream, uint8_t val)
  191. {
  192. /* for a byte, we don't need to endian-swap */
  193. return cork_stream_consumer_data(stream, &val, sizeof(uint8_t), false);
  194. }
  195. /**
  196. * Write a big-endian uint16 to a stream. If we can't write the
  197. * integer for some reason, return an error.
  198. */
  199. static int
  200. write_uint16(struct cork_stream_consumer *stream, uint16_t val)
  201. {
  202. CORK_UINT16_HOST_TO_BIG_IN_PLACE(val);
  203. return cork_stream_consumer_data(stream, &val, sizeof(uint16_t), false);
  204. }
  205. /**
  206. * Write a big-endian uint32 to a stream. If we can't write the
  207. * integer for some reason, return an error.
  208. */
  209. static int
  210. write_uint32(struct cork_stream_consumer *stream, uint32_t val)
  211. {
  212. CORK_UINT32_HOST_TO_BIG_IN_PLACE(val);
  213. return cork_stream_consumer_data(stream, &val, sizeof(uint32_t), false);
  214. }
  215. /**
  216. * Write a big-endian uint64 to a stream. If we can't write the
  217. * integer for some reason, return an error.
  218. */
  219. static int
  220. write_uint64(struct cork_stream_consumer *stream, uint64_t val)
  221. {
  222. CORK_UINT64_HOST_TO_BIG_IN_PLACE(val);
  223. return cork_stream_consumer_data(stream, &val, sizeof(uint64_t), false);
  224. }
  225. /*-----------------------------------------------------------------------
  226. * V1 BDD file
  227. */
  228. static const char MAGIC_NUMBER[] = "IP set";
  229. static const size_t MAGIC_NUMBER_LENGTH = sizeof(MAGIC_NUMBER) - 1;
  230. static int
  231. write_header_v1(struct save_data *save_data,
  232. struct ipset_node_cache *cache, ipset_node_id root)
  233. {
  234. /* Output the magic number for an IP set, and the file format
  235. * version that we're going to write. */
  236. rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true));
  237. rii_check(write_string(save_data->stream, MAGIC_NUMBER));
  238. rii_check(write_uint16(save_data->stream, 0x0001));
  239. /* Determine how many reachable nodes there are, to calculate the
  240. * size of the set. */
  241. size_t nonterminal_count = ipset_node_reachable_count(cache, root);
  242. size_t set_size =
  243. MAGIC_NUMBER_LENGTH + /* magic number */
  244. sizeof(uint16_t) + /* version number */
  245. sizeof(uint64_t) + /* length of set */
  246. sizeof(uint32_t) + /* number of nonterminals */
  247. (nonterminal_count * /* for each nonterminal: */
  248. (sizeof(uint8_t) + /* variable number */
  249. sizeof(uint32_t) + /* low pointer */
  250. sizeof(uint32_t) /* high pointer */
  251. ));
  252. /* If the root is a terminal, we need to add 4 bytes to the set
  253. * size, for storing the terminal value. */
  254. if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) {
  255. set_size += sizeof(uint32_t);
  256. }
  257. rii_check(write_uint64(save_data->stream, set_size));
  258. rii_check(write_uint32(save_data->stream, nonterminal_count));
  259. return 0;
  260. }
  261. static int
  262. write_footer_v1(struct save_data *save_data,
  263. struct ipset_node_cache *cache, ipset_node_id root)
  264. {
  265. /* If the root is a terminal node, then we output the terminal value
  266. * in place of the (nonexistent) list of nonterminal nodes. */
  267. if (ipset_node_get_type(root) == IPSET_TERMINAL_NODE) {
  268. ipset_value value = ipset_terminal_value(root);
  269. return write_uint32(save_data->stream, value);
  270. }
  271. return 0;
  272. }
  273. static int
  274. write_terminal_v1(struct save_data *save_data, ipset_value terminal_value)
  275. {
  276. /* We don't have to write anything out for a terminal in a V1 file,
  277. * since the terminal's value will be encoded into the node ID
  278. * wherever it's used. */
  279. return 0;
  280. }
  281. static int
  282. write_nonterminal_v1(struct save_data *save_data,
  283. serialized_id serialized_node,
  284. ipset_variable variable,
  285. serialized_id serialized_low,
  286. serialized_id serialized_high)
  287. {
  288. rii_check(write_uint8(save_data->stream, variable));
  289. rii_check(write_uint32(save_data->stream, serialized_low));
  290. rii_check(write_uint32(save_data->stream, serialized_high));
  291. return 0;
  292. }
  293. int
  294. ipset_node_cache_save(struct cork_stream_consumer *stream, struct ipset_node_cache *cache,
  295. ipset_node_id node)
  296. {
  297. struct save_data save_data;
  298. save_data.cache = cache;
  299. save_data.stream = stream;
  300. save_data.write_header = write_header_v1;
  301. save_data.write_footer = write_footer_v1;
  302. save_data.write_terminal = write_terminal_v1;
  303. save_data.write_nonterminal = write_nonterminal_v1;
  304. return save_bdd(&save_data, cache, node);
  305. }
  306. /*-----------------------------------------------------------------------
  307. * GraphViz dot file
  308. */
  309. static const char *GRAPHVIZ_HEADER =
  310. "strict digraph bdd {\n";
  311. static const char *GRAPHVIZ_FOOTER =
  312. "}\n";
  313. struct dot_data {
  314. /* The terminal value to leave out of the dot file. This should be
  315. * the default value of the set or map. */
  316. ipset_value default_value;
  317. /* A scratch buffer */
  318. struct cork_buffer scratch;
  319. };
  320. static int
  321. write_header_dot(struct save_data *save_data,
  322. struct ipset_node_cache *cache, ipset_node_id root)
  323. {
  324. /* Output the opening clause of the GraphViz script. */
  325. rii_check(cork_stream_consumer_data(save_data->stream, NULL, 0, true));
  326. return write_string(save_data->stream, GRAPHVIZ_HEADER);
  327. }
  328. static int
  329. write_footer_dot(struct save_data *save_data,
  330. struct ipset_node_cache *cache, ipset_node_id root)
  331. {
  332. /* Output the closing clause of the GraphViz script. */
  333. return write_string(save_data->stream, GRAPHVIZ_FOOTER);
  334. }
  335. static int
  336. write_terminal_dot(struct save_data *save_data, ipset_value terminal_value)
  337. {
  338. struct dot_data *dot_data = save_data->user_data;
  339. /* If this terminal has the default value, skip it. */
  340. if (terminal_value == dot_data->default_value) {
  341. return 0;
  342. }
  343. /* Output a node for the terminal value. */
  344. cork_buffer_printf
  345. (&dot_data->scratch,
  346. " t%d [shape=box, label=%d];\n",
  347. terminal_value, terminal_value);
  348. return write_string(save_data->stream, dot_data->scratch.buf);
  349. }
  350. static int
  351. write_nonterminal_dot(struct save_data *save_data,
  352. serialized_id serialized_node,
  353. ipset_variable variable,
  354. serialized_id serialized_low,
  355. serialized_id serialized_high)
  356. {
  357. struct dot_data *dot_data = save_data->user_data;
  358. /* Include a node for the nonterminal value. */
  359. cork_buffer_printf
  360. (&dot_data->scratch,
  361. " n%d [shape=circle,label=%u];\n",
  362. (-serialized_node), variable);
  363. /* Include an edge for the low pointer. */
  364. if (serialized_low < 0) {
  365. /* The low pointer is a nonterminal. */
  366. cork_buffer_append_printf
  367. (&dot_data->scratch,
  368. " n%d -> n%d",
  369. (-serialized_node), (-serialized_low));
  370. } else {
  371. /* The low pointer is a terminal. */
  372. ipset_value low_value = (ipset_value) serialized_low;
  373. if (low_value == dot_data->default_value) {
  374. /* The terminal is the default value, so instead of a real
  375. * terminal, connect this pointer to a dummy circle node. */
  376. cork_buffer_append_printf
  377. (&dot_data->scratch,
  378. " low%d [shape=circle,label=\"\"]\n"
  379. " n%d -> low%d",
  380. (-serialized_node), (-serialized_node), (-serialized_node));
  381. } else {
  382. /* The terminal isn't a default, so go ahead and output it. */
  383. cork_buffer_append_printf
  384. (&dot_data->scratch,
  385. " n%d -> t%d",
  386. (-serialized_node), serialized_low);
  387. }
  388. }
  389. cork_buffer_append_printf
  390. (&dot_data->scratch, " [style=dashed,color=red]\n");
  391. /* Include an edge for the high pointer. */
  392. if (serialized_high < 0) {
  393. /* The high pointer is a nonterminal. */
  394. cork_buffer_append_printf
  395. (&dot_data->scratch,
  396. " n%d -> n%d",
  397. (-serialized_node), (-serialized_high));
  398. } else {
  399. /* The high pointer is a terminal. */
  400. ipset_value high_value = (ipset_value) serialized_high;
  401. if (high_value == dot_data->default_value) {
  402. /* The terminal is the default value, so instead of a real
  403. * terminal, connect this pointer to a dummy circle node. */
  404. cork_buffer_append_printf
  405. (&dot_data->scratch,
  406. " high%d "
  407. "[shape=circle,"
  408. "fixedsize=true,"
  409. "height=0.25,"
  410. "width=0.25,"
  411. "label=\"\"]\n"
  412. " n%d -> high%d",
  413. (-serialized_node), (-serialized_node), (-serialized_node));
  414. } else {
  415. /* The terminal isn't a default, so go ahead and output it. */
  416. cork_buffer_append_printf
  417. (&dot_data->scratch,
  418. " n%d -> t%d",
  419. (-serialized_node), serialized_high);
  420. }
  421. }
  422. cork_buffer_append_printf
  423. (&dot_data->scratch, " [style=solid,color=black]\n");
  424. /* Output the clauses to the stream. */
  425. return write_string(save_data->stream, dot_data->scratch.buf);
  426. }
  427. int
  428. ipset_node_cache_save_dot(struct cork_stream_consumer *stream,
  429. struct ipset_node_cache *cache, ipset_node_id node)
  430. {
  431. struct dot_data dot_data = {
  432. 0 /* default value */
  433. };
  434. struct save_data save_data;
  435. save_data.cache = cache;
  436. save_data.stream = stream;
  437. save_data.write_header = write_header_dot;
  438. save_data.write_footer = write_footer_dot;
  439. save_data.write_terminal = write_terminal_dot;
  440. save_data.write_nonterminal = write_nonterminal_dot;
  441. save_data.user_data = &dot_data;
  442. return save_bdd(&save_data, cache, node);
  443. }