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.

350 lines
9.8 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 <errno.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <libcork/core.h>
  14. #include <libcork/ds.h>
  15. #include <libcork/helpers/errors.h>
  16. #include "ipset/bdd/nodes.h"
  17. #include "ipset/errors.h"
  18. #include "ipset/logging.h"
  19. static const char MAGIC_NUMBER[] = "IP set";
  20. static const size_t MAGIC_NUMBER_LENGTH = sizeof(MAGIC_NUMBER) - 1;
  21. /**
  22. * On disk, we use a different node ID scheme than we do in memory.
  23. * Terminal node IDs are non-negative, and are equal to the terminal
  24. * value. Nonterminal node IDs are negative, starting with -1.
  25. * Nonterminal -1 appears first on disk, then nonterminal -2, and so on.
  26. */
  27. typedef int serialized_id;
  28. /**
  29. * Sets a libcork error based on the contents of errno.
  30. */
  31. static void
  32. create_errno_error(FILE *stream)
  33. {
  34. if (ferror(stream)) {
  35. cork_error_set(IPSET_ERROR, IPSET_IO_ERROR, "%s", strerror(errno));
  36. } else {
  37. cork_unknown_error();
  38. }
  39. }
  40. /**
  41. * Read in a big-endian uint8 from a stream. If we can't read the
  42. * integer for some reason, return an error.
  43. */
  44. static int
  45. read_uint8(FILE *stream, uint8_t *dest)
  46. {
  47. size_t num_read = fread(dest, sizeof(uint8_t), 1, stream);
  48. if (num_read != 1) {
  49. create_errno_error(stream);
  50. return -1;
  51. }
  52. /* for a byte, we don't need to endian-swap */
  53. return 0;
  54. }
  55. /**
  56. * Read in a big-endian uint16 from a stream. If we can't read the
  57. * integer for some reason, return an error.
  58. */
  59. static uint16_t
  60. read_uint16(FILE *stream, uint16_t *dest)
  61. {
  62. size_t num_read = fread(dest, sizeof(uint16_t), 1, stream);
  63. if (num_read != 1) {
  64. create_errno_error(stream);
  65. return -1;
  66. }
  67. CORK_UINT16_BIG_TO_HOST_IN_PLACE(*dest);
  68. return 0;
  69. }
  70. /**
  71. * Read in a big-endian uint32 from a stream. If we can't read the
  72. * integer for some reason, return an error.
  73. */
  74. static uint32_t
  75. read_uint32(FILE *stream, uint32_t *dest)
  76. {
  77. size_t num_read = fread(dest, sizeof(uint32_t), 1, stream);
  78. if (num_read != 1) {
  79. create_errno_error(stream);
  80. return -1;
  81. }
  82. CORK_UINT32_BIG_TO_HOST_IN_PLACE(*dest);
  83. return 0;
  84. }
  85. /**
  86. * Read in a big-endian uint64 from a stream. If we can't read the
  87. * integer for some reason, return an error.
  88. */
  89. static uint64_t
  90. read_uint64(FILE *stream, uint64_t *dest)
  91. {
  92. size_t num_read = fread(dest, sizeof(uint64_t), 1, stream);
  93. if (num_read != 1) {
  94. create_errno_error(stream);
  95. return -1;
  96. }
  97. CORK_UINT64_BIG_TO_HOST_IN_PLACE(*dest);
  98. return 0;
  99. }
  100. /**
  101. * A helper function that verifies that we've read exactly as many bytes
  102. * as we should, returning an error otherwise.
  103. */
  104. static int
  105. verify_cap(size_t bytes_read, size_t cap)
  106. {
  107. if (bytes_read < cap) {
  108. /* There's extra data at the end of the stream. */
  109. cork_error_set
  110. (IPSET_ERROR, IPSET_PARSE_ERROR,
  111. "Malformed set: extra data at end of stream.");
  112. return -1;
  113. } else if (bytes_read > cap) {
  114. /* We read more data than we were supposed to. */
  115. cork_error_set
  116. (IPSET_ERROR, IPSET_PARSE_ERROR,
  117. "Malformed set: read too much data.");
  118. return -1;
  119. }
  120. return 0;
  121. }
  122. /**
  123. * A helper function for reading a version 1 BDD stream.
  124. */
  125. static ipset_node_id
  126. load_v1(FILE *stream, struct ipset_node_cache *cache)
  127. {
  128. DEBUG("Stream contains v1 IP set");
  129. ipset_node_id result;
  130. struct cork_hash_table *cache_ids = cork_pointer_hash_table_new(0, 0);
  131. /* We've already read in the magic number and version. Next should
  132. * be the length of the encoded set. */
  133. uint64_t length;
  134. DEBUG("Reading encoded length");
  135. ei_check(read_uint64(stream, &length));
  136. /* The length includes the magic number, version number, and the
  137. * length field itself. Remove those to get the cap on the
  138. * remaining stream. */
  139. size_t bytes_read = 0;
  140. size_t cap = length -
  141. MAGIC_NUMBER_LENGTH -
  142. sizeof(uint16_t) -
  143. sizeof(uint64_t);
  144. DEBUG("Length cap is %zu bytes.", cap);
  145. /* Read in the number of nonterminals. */
  146. uint32_t nonterminal_count;
  147. DEBUG("Reading number of nonterminals");
  148. ei_check(read_uint32(stream, &nonterminal_count));
  149. bytes_read += sizeof(uint32_t);
  150. /* If there are no nonterminals, then there's only a single terminal
  151. * left to read. */
  152. if (nonterminal_count == 0) {
  153. uint32_t value;
  154. DEBUG("Reading single terminal value");
  155. ei_check(read_uint32(stream, &value));
  156. bytes_read += sizeof(uint32_t);
  157. /* We should have reached the end of the encoded set. */
  158. ei_check(verify_cap(bytes_read, cap));
  159. /* Create a terminal node for this value and return it. */
  160. cork_hash_table_free(cache_ids);
  161. return ipset_terminal_node_id(value);
  162. }
  163. /* Otherwise, read in each nonterminal. We need to keep track of a
  164. * mapping between each nonterminal's ID in the stream (which are
  165. * number consecutively from -1), and its ID in the node cache
  166. * (which could be anything). */
  167. size_t i;
  168. for (i = 0; i < nonterminal_count; i++) {
  169. serialized_id serialized_id = -(i+1);
  170. /* Each serialized node consists of a variable index, a low
  171. * pointer, and a high pointer. */
  172. uint8_t variable;
  173. ei_check(read_uint8(stream, &variable));
  174. bytes_read += sizeof(uint8_t);
  175. int32_t low;
  176. ei_check(read_uint32(stream, (uint32_t *) &low));
  177. bytes_read += sizeof(int32_t);
  178. int32_t high;
  179. ei_check(read_uint32(stream, (uint32_t *) &high));
  180. bytes_read += sizeof(int32_t);
  181. DEBUG("Read serialized node %d = (x%d? %" PRId32 ": %" PRId32 ")",
  182. serialized_id, variable, high, low);
  183. /* Turn the low pointer into a node ID. If the pointer is >= 0,
  184. * it's a terminal value. Otherwise, its a nonterminal ID,
  185. * indexing into the serialized nonterminal array.*/
  186. ipset_node_id low_id;
  187. if (low >= 0) {
  188. low_id = ipset_terminal_node_id(low);
  189. } else {
  190. /* The file format guarantees that any node reference points
  191. * to a node earlier in the serialized array. That means we
  192. * can assume that cache_ids has already been filled in for
  193. * this node. */
  194. low_id = (ipset_node_id) (uintptr_t)
  195. cork_hash_table_get(cache_ids, (void *) (intptr_t) low);
  196. DEBUG(" Serialized ID %" PRId32 " is internal ID %u",
  197. low, low_id);
  198. }
  199. /* Do the same for the high pointer. */
  200. ipset_node_id high_id;
  201. if (high >= 0) {
  202. high_id = ipset_terminal_node_id(high);
  203. } else {
  204. /* The file format guarantees that any node reference points
  205. * to a node earlier in the serialized array. That means we
  206. * can assume that cache_ids has already been filled in for
  207. * this node. */
  208. high_id = (ipset_node_id) (uintptr_t)
  209. cork_hash_table_get(cache_ids, (void *) (intptr_t) high);
  210. DEBUG(" Serialized ID %" PRId32 " is internal ID %u",
  211. high, high_id);
  212. }
  213. /* Create a nonterminal node in the node cache. */
  214. result = ipset_node_cache_nonterminal
  215. (cache, variable, low_id, high_id);
  216. DEBUG("Internal node %u = nonterminal(x%d? %u: %u)",
  217. result, (int) variable, high_id, low_id);
  218. /* Remember the internal node ID for this new node, in case any
  219. * later serialized nodes point to it. */
  220. cork_hash_table_put
  221. (cache_ids, (void *) (intptr_t) serialized_id,
  222. (void *) (uintptr_t) result, NULL, NULL, NULL);
  223. }
  224. /* We should have reached the end of the encoded set. */
  225. ei_check(verify_cap(bytes_read, cap));
  226. /* The last node is the nonterminal for the entire set. */
  227. cork_hash_table_free(cache_ids);
  228. return result;
  229. error:
  230. /* If there's an error, clean up the objects that we've created
  231. * before returning. */
  232. cork_hash_table_free(cache_ids);
  233. return 0;
  234. }
  235. ipset_node_id
  236. ipset_node_cache_load(FILE *stream, struct ipset_node_cache *cache)
  237. {
  238. size_t bytes_read;
  239. /* First, read in the magic number from the stream to ensure that
  240. * this is an IP set. */
  241. uint8_t magic[MAGIC_NUMBER_LENGTH];
  242. DEBUG("Reading IP set magic number");
  243. bytes_read = fread(magic, 1, MAGIC_NUMBER_LENGTH, stream);
  244. if (ferror(stream)) {
  245. create_errno_error(stream);
  246. return 0;
  247. }
  248. if (bytes_read != MAGIC_NUMBER_LENGTH) {
  249. /* We reached EOF before reading the entire magic number. */
  250. cork_error_set
  251. (IPSET_ERROR, IPSET_PARSE_ERROR,
  252. "Unexpected end of file");
  253. return 0;
  254. }
  255. if (memcmp(magic, MAGIC_NUMBER, MAGIC_NUMBER_LENGTH) != 0) {
  256. /* The magic number doesn't match, so this isn't a BDD. */
  257. cork_error_set
  258. (IPSET_ERROR, IPSET_PARSE_ERROR,
  259. "Magic number doesn't match; this isn't an IP set.");
  260. return 0;
  261. }
  262. /* Read in the version number and dispatch to the right reading
  263. * function. */
  264. uint16_t version;
  265. DEBUG("Reading IP set version");
  266. xi_check(0, read_uint16(stream, &version));
  267. switch (version) {
  268. case 0x0001:
  269. return load_v1(stream, cache);
  270. default:
  271. /* We don't know how to read this version number. */
  272. cork_error_set
  273. (IPSET_ERROR, IPSET_PARSE_ERROR,
  274. "Unknown version number %" PRIu16, version);
  275. return 0;
  276. }
  277. }